<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Dr. Michael Lesniak | Freelance Java Software Engineer</title>
    <link>https://mlesniak.com/</link>
    <description>Recent content on Dr. Michael Lesniak | Freelance Java Software Engineer</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <managingEditor>mail@mlesniak.com (Dr. Michael Lesniak)</managingEditor>
    <webMaster>mail@mlesniak.com (Dr. Michael Lesniak)</webMaster>
    <lastBuildDate>Fri, 03 Jan 2025 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://mlesniak.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>You could have invented it yourself: Dependency Injection</title>
      <link>https://mlesniak.com/articles/di/</link>
      <pubDate>Fri, 03 Jan 2025 00:00:00 +0000</pubDate><author>mail@mlesniak.com (Dr. Michael Lesniak)</author>
      <guid>https://mlesniak.com/articles/di/</guid>
      <description>&lt;h2 id=&#34;why-do-we-need-dependency-injection&#34;&gt;Why do we need Dependency Injection?&lt;/h2&gt;
&lt;p&gt;Dependency Injection (DI) addresses a simple but critical problem: keeping your
code manageable. Without it, you’re stuck with tightly coupled components, like
a Car class that directly creates its own Engine.&lt;/p&gt;
&lt;p&gt;Initially, this seems fine. But what happens when you need different engines,
gas, electric, or something new? Suddenly, your Car class has to handle creation
logic, breaking its focus and making changes a nightmare.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="why-do-we-need-dependency-injection">Why do we need Dependency Injection?</h2>
<p>Dependency Injection (DI) addresses a simple but critical problem: keeping your
code manageable. Without it, you’re stuck with tightly coupled components, like
a Car class that directly creates its own Engine.</p>
<p>Initially, this seems fine. But what happens when you need different engines,
gas, electric, or something new? Suddenly, your Car class has to handle creation
logic, breaking its focus and making changes a nightmare.</p>
<p>DI changes the approach. Instead of creating its own dependencies, the Car
gets its
Engine from the outside. This keeps your code focused, flexible, and easy to
test. Swap an engine? Update a configuration? No problem.</p>
<p>You don’t need a fancy framework to do DI. But frameworks like Spring automate
the wiring, saving time. The payoff? Cleaner code, fewer headaches, and a
codebase that doesn’t fight you every time something changes.</p>
<p>From the outside, it might seem like magic. Here&rsquo;s how you can implement it yourself.</p>
<h2 id="the-canonical-spring-example">The canonical Spring example</h2>
<p>The simplest example consists of a (Spring) component <code>MessageConsumer</code> which
depends on another (Spring) component <code>MessageProvider</code>, which in turn does not
have any further dependencies. When starting the application, the <code>run</code>
method from the consumer is called since it implements <code>CommandLineRunner</code>:</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#999;font-style:italic">// Main.java</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">org.springframework.boot.SpringApplication</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">org.springframework.boot.autoconfigure.SpringBootApplication</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#ffa500">@SpringBootApplication</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">class</span> <span style="color:#447fcf;text-decoration:underline">Main</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">void</span><span style="color:#666"> </span><span style="color:#447fcf">main</span>(String...<span style="color:#666"> </span>args)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#666">        </span>SpringApplication.<span style="color:#bbb">run</span>(Main.<span style="color:#bbb">class</span>,<span style="color:#666"> </span>args);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span><span style="color:#666">    </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span><span style="color:#999;font-style:italic">// MessageConsumer.java</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">org.springframework.boot.CommandLineRunner</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">org.springframework.stereotype.Component</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span><span style="color:#ffa500">@Component</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">18</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">class</span> <span style="color:#447fcf;text-decoration:underline">MessageConsumer</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">implements</span><span style="color:#666"> </span>CommandLineRunner<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">19</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">final</span><span style="color:#666"> </span>MessageProvider<span style="color:#666"> </span>messageProvider;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">20</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">21</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#447fcf">MessageConsumer</span>(MessageProvider<span style="color:#666"> </span>messageProvider)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">22</span><span><span style="color:#666">        </span><span style="color:#6ab825;font-weight:bold">this</span>.<span style="color:#bbb">messageProvider</span><span style="color:#666"> </span>=<span style="color:#666"> </span>messageProvider;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">23</span><span><span style="color:#666">    </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">24</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">25</span><span><span style="color:#666">    </span><span style="color:#ffa500">@Override</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">26</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">void</span><span style="color:#666"> </span><span style="color:#447fcf">run</span>(String...<span style="color:#666"> </span>args)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">27</span><span><span style="color:#666">        </span>String<span style="color:#666"> </span>message<span style="color:#666"> </span>=<span style="color:#666"> </span>messageProvider.<span style="color:#bbb">getMessage</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">28</span><span><span style="color:#666">        </span>System.<span style="color:#bbb">out</span>.<span style="color:#bbb">println</span>(message);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">29</span><span><span style="color:#666">    </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">30</span><span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">31</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">32</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">33</span><span><span style="color:#999;font-style:italic">// MessageProvider.java</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">34</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">org.springframework.stereotype.Component</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">35</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">36</span><span><span style="color:#ffa500">@Component</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">37</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">class</span> <span style="color:#447fcf;text-decoration:underline">MessageProvider</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">38</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span>String<span style="color:#666"> </span><span style="color:#447fcf">getMessage</span>()<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">39</span><span><span style="color:#666">        </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span><span style="color:#ed9d13">&#34;Hello, world&#34;</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">40</span><span><span style="color:#666">    </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">41</span><span>}</span></span></code></pre></div>
<p>In the following sections, we will implement our own dependency injection
framework. Our goal is to allow seamless switching of import statements to our implementation without any other changes.
The example should still function as expected.</p>
<p>Our goal:
<img src="https://mlesniak.com/articles/di.png" /></p>
<h2 id="our-own-implementation">Our own implementation</h2>
<p>Spring&rsquo;s DI framework offers extensive functionality, much of which is beyond the scope of this educational example.
Let’s begin by clarifying what we won’t implement, though much of it is
straightforward:</p>
<ul>
<li>Lifecycle Management. No support for <code>@PostConstruct</code>, <code>@PreDestroy</code>, &hellip;</li>
<li>Scope Management. We only support singletons. Adding additional types such
as Request, Prototype, Session would be possible by adding a dedicated
<code>ApplicationContext</code> and appropriate methods. Since we do not support
<code>@Controller</code> in our small example, these do not make sense anyway.</li>
<li>No Aspect-oriented programming (AOP). This might be a bit more tricky and
will probably be covered in a future post.</li>
<li>No field or method injection. Technically, trivial to implement, but would
just blow up the code without providing additional insights.</li>
</ul>
<p>&hellip; and, as always, we implement minimal error and edge case handling &ndash;
definitely not production-ready. ;-)</p>
<p>Having said all that, let&rsquo;s start with modifications to our <code>Main</code> class.
Since the name <code>SpringApplication</code> is already reserved, and summer follows on
spring, let&rsquo;s call it &hellip; <code>SummerApplication</code>:</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">1</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">com.mlesniak.boot.SummerApplication</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">2</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">3</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">class</span> <span style="color:#447fcf;text-decoration:underline">Main</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">4</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">void</span><span style="color:#666"> </span><span style="color:#447fcf">main</span>(String...<span style="color:#666"> </span>args)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">5</span><span><span style="color:#666">        </span>SummerApplication.<span style="color:#bbb">run</span>(Main.<span style="color:#bbb">class</span>,<span style="color:#666"> </span>args);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">6</span><span><span style="color:#666">    </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">7</span><span>}</span></span></code></pre></div>
<p>The remaining files stay the same, though, as promised, we change the imports:</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#999;font-style:italic">// MessageProvider.java</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">com.mlesniak.boot.Component</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#ffa500">@Component</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">class</span> <span style="color:#447fcf;text-decoration:underline">MessageProvider</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span>String<span style="color:#666"> </span><span style="color:#447fcf">getMessage</span>()<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span><span style="color:#666">        </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span><span style="color:#ed9d13">&#34;Hello, world&#34;</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#666">    </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#999;font-style:italic">// MessageConsumer.java</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">com.mlesniak.boot.CommandLineRunner</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">com.mlesniak.boot.Component</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span><span style="color:#ffa500">@Component</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">class</span> <span style="color:#447fcf;text-decoration:underline">MessageConsumer</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">implements</span><span style="color:#666"> </span>CommandLineRunner<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span><span style="color:#666">  </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">final</span><span style="color:#666"> </span>MessageProvider<span style="color:#666"> </span>messageProvider;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">18</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">19</span><span><span style="color:#666">  </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#447fcf">MessageConsumer</span>(MessageProvider<span style="color:#666"> </span>messageProvider)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">20</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">this</span>.<span style="color:#bbb">messageProvider</span><span style="color:#666"> </span>=<span style="color:#666"> </span>messageProvider;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">21</span><span><span style="color:#666">  </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">22</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">23</span><span><span style="color:#666">  </span><span style="color:#ffa500">@Override</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">24</span><span><span style="color:#666">  </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">void</span><span style="color:#666"> </span><span style="color:#447fcf">run</span>(String...<span style="color:#666"> </span>args)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">25</span><span><span style="color:#666">    </span>String<span style="color:#666"> </span>message<span style="color:#666"> </span>=<span style="color:#666"> </span>messageProvider.<span style="color:#bbb">getMessage</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">26</span><span><span style="color:#666">    </span>System.<span style="color:#bbb">out</span>.<span style="color:#bbb">println</span>(message);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">27</span><span><span style="color:#666">  </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">28</span><span>}</span></span></code></pre></div>
<h3 id="marking-things">Marking things&hellip;</h3>
<p>Since we do not use Controllers but still want an entrypoint, let&rsquo;s define our
own marker interface:</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">1</span><span><span style="color:#6ab825;font-weight:bold">package</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">com.mlesniak.boot</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">2</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">3</span><span><span style="color:#999;font-style:italic">/// Marker interface to determine where our application</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">4</span><span><span style="color:#999;font-style:italic">/// starts since we do not have typical controllers</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">5</span><span><span style="color:#999;font-style:italic">/// waiting for HTTP requests.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">6</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">interface</span> <span style="color:#447fcf;text-decoration:underline">CommandLineRunner</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">7</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">void</span><span style="color:#666"> </span><span style="color:#447fcf">run</span>(String...<span style="color:#666"> </span>args);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">8</span><span>}</span></span></code></pre></div>
<p>We also want to annotate service classes with our own Component annotation</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#6ab825;font-weight:bold">package</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">com.mlesniak.boot</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.lang.annotation.ElementType</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.lang.annotation.Retention</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.lang.annotation.RetentionPolicy</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.lang.annotation.Target</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#999;font-style:italic">///</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span><span style="color:#999;font-style:italic">/// Marker interface to determine components of our application.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span><span style="color:#999;font-style:italic">///</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#999;font-style:italic">/// For every class marked as a component, we try to resolve all</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#999;font-style:italic">/// dependencies in its constructor.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span><span style="color:#ffa500">@Retention</span>(RetentionPolicy.<span style="color:#bbb">RUNTIME</span>)<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span><span style="color:#ffa500">@Target</span>(ElementType.<span style="color:#bbb">TYPE</span>)<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#ffa500">@interface</span><span style="color:#666"> </span>Component<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span>}</span></span></code></pre></div>
<h3 id="the-interesting-part">The interesting part</h3>
<p>The actual magic is implemented in the following 140 lines of code. We walk
through them step by step.</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#6ab825;font-weight:bold">package</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">com.mlesniak.boot</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">com.mlesniak.Main</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.io.IOException</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.lang.reflect.InvocationTargetException</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.net.URI</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.net.URISyntaxException</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.nio.file.*</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.util.*</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#6ab825;font-weight:bold">import</span><span style="color:#666"> </span><span style="color:#447fcf;text-decoration:underline">java.util.stream.Collectors</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span><span style="color:#999;font-style:italic">/// Core dependency injection resolution.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">class</span> <span style="color:#447fcf;text-decoration:underline">SummerApplication</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">// ... to be filled out in this section ...</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span>}</span></span></code></pre></div>
<p>Our sole public method is <code>run</code>, which serves to purposes:</p>
<ol>
<li>Create all necessary singletons</li>
<li>Figure out the component implementing <code>CommandLineRunner</code> and start them.</li>
</ol>
<p>Therefore, the code is straightforward:</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// Entry point into dependency injection.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">///</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// @param mainClass The main class of the application, ideally placed at the root package.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// @param args      Command line args.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#666">  </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">void</span><span style="color:#666"> </span><span style="color:#447fcf">run</span>(Class&lt;Main&gt;<span style="color:#666"> </span>mainClass,<span style="color:#666"> </span>String[]<span style="color:#666"> </span>args)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#666">      </span>List&lt;Class&lt;?&gt;&gt;<span style="color:#666"> </span>components<span style="color:#666"> </span>=<span style="color:#666"> </span>getComponents(mainClass);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#666">      </span><span style="color:#999;font-style:italic">// For our example, we support only singletons.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span><span style="color:#666">      </span>Map&lt;Class&lt;?&gt;,<span style="color:#666"> </span>Object&gt;<span style="color:#666"> </span>instances<span style="color:#666"> </span>=<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>HashMap&lt;&gt;();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span><span style="color:#666">      </span>components.<span style="color:#bbb">forEach</span>(component<span style="color:#666"> </span>-&gt;<span style="color:#666"> </span>createSingleton(instances,<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>HashSet&lt;&gt;(),<span style="color:#666"> </span>component));<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#666">      </span><span style="color:#999;font-style:italic">// Find entry point by looking for the class implementing CommandLineRunner. </span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>entryClasses<span style="color:#666"> </span>=<span style="color:#666"> </span>instances<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span><span style="color:#666">              </span>.<span style="color:#bbb">keySet</span>().<span style="color:#bbb">stream</span>()<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span><span style="color:#666">              </span>.<span style="color:#bbb">filter</span>(SummerApplication::hasCommandLineRunnerInterface)<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span><span style="color:#666">              </span>.<span style="color:#bbb">toList</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">if</span><span style="color:#666"> </span>(entryClasses.<span style="color:#bbb">isEmpty</span>())<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">18</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">throw</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>IllegalStateException(<span style="color:#ed9d13">&#34;No entry point defined via CommandLineRunner&#34;</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">19</span><span><span style="color:#666">      </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">20</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">if</span><span style="color:#666"> </span>(entryClasses.<span style="color:#bbb">size</span>()<span style="color:#666"> </span>&gt;<span style="color:#666"> </span>1)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">21</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">throw</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>IllegalStateException(<span style="color:#ed9d13">&#34;Ambiguous entry points defined via CommandLineRunner&#34;</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">22</span><span><span style="color:#666">      </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">23</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>entryClass<span style="color:#666"> </span>=<span style="color:#666"> </span>entryClasses.<span style="color:#bbb">getFirst</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">24</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">25</span><span><span style="color:#666">      </span>((CommandLineRunner)<span style="color:#666"> </span>instances.<span style="color:#bbb">get</span>(entryClass)).<span style="color:#bbb">run</span>(args);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">26</span><span><span style="color:#666">  </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">27</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">28</span><span><span style="color:#666">  </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">boolean</span><span style="color:#666"> </span><span style="color:#447fcf">hasCommandLineRunnerInterface</span>(Class&lt;?&gt;<span style="color:#666"> </span>clazz)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">29</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span>Arrays.<span style="color:#bbb">stream</span>(clazz.<span style="color:#bbb">getInterfaces</span>())<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">30</span><span><span style="color:#666">            </span>.<span style="color:#bbb">anyMatch</span>(i<span style="color:#666"> </span>-&gt;<span style="color:#666"> </span>i<span style="color:#666"> </span>==<span style="color:#666"> </span>CommandLineRunner.<span style="color:#bbb">class</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">31</span><span><span style="color:#666">  </span>}</span></span></code></pre></div>
<p>A key function is <code>getComponents</code>. We need to collect all classes annotated
with our custom component annotation on the classpath:</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// Get a list of all components based on the passed package of the class.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">///</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// @param mainClass the root class to start scanning.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#666">  </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span>List&lt;Class&lt;?&gt;&gt;<span style="color:#666"> </span>getComponents(Class&lt;Main&gt;<span style="color:#666"> </span>mainClass)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">try</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span>findAllClassesInPackage(mainClass.<span style="color:#bbb">getPackageName</span>()).<span style="color:#bbb">stream</span>()<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span><span style="color:#666">                  </span>.<span style="color:#bbb">filter</span>(c<span style="color:#666"> </span>-&gt;<span style="color:#666"> </span>c.<span style="color:#bbb">getAnnotation</span>(Component.<span style="color:#bbb">class</span>)<span style="color:#666"> </span>!=<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">null</span>)<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#666">                  </span>.<span style="color:#bbb">collect</span>(Collectors.<span style="color:#bbb">toList</span>());<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span><span style="color:#666">      </span>}<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">catch</span><span style="color:#666"> </span>(IOException<span style="color:#666"> </span>|<span style="color:#666"> </span>URISyntaxException<span style="color:#666"> </span>e)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">throw</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>IllegalStateException(<span style="color:#ed9d13">&#34;Error retrieving components, starting at &#34;</span><span style="color:#666"> </span>+<span style="color:#666"> </span>mainClass.<span style="color:#bbb">getSimpleName</span>(),<span style="color:#666"> </span>e);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#666">      </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#666">  </span>}</span></span></code></pre></div>
<p>This is slightly complicated since we can either run our application with an unpacked
classpath, e.g. on the command line or via an IDE, or as a packed (fat) .
jar-file.</p>
<p>Thanks to the abstraction provided by <code>java.nio</code>, this can be handled quite
elegantly:</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// Retrieve a list of all classes in a package (or its children). This method supports both unpacked</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// (target/classes) and packed (.jar) class containers.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#666">  </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span>Set&lt;Class&lt;?&gt;&gt;<span style="color:#666"> </span>findAllClassesInPackage(String<span style="color:#666"> </span>packageName)<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">throws</span><span style="color:#666"> </span>IOException,<span style="color:#666"> </span>URISyntaxException<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#666">      </span>String<span style="color:#666"> </span>path<span style="color:#666"> </span>=<span style="color:#666"> </span>packageName.<span style="color:#bbb">replace</span>(<span style="color:#ed9d13">&#39;.&#39;</span>,<span style="color:#666"> </span><span style="color:#ed9d13">&#39;/&#39;</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#666">      </span>URI<span style="color:#666"> </span>uri<span style="color:#666"> </span>=<span style="color:#666"> </span>SummerApplication.<span style="color:#bbb">class</span>.<span style="color:#bbb">getResource</span>(<span style="color:#ed9d13">&#34;/&#34;</span><span style="color:#666"> </span>+<span style="color:#666"> </span>path).<span style="color:#bbb">toURI</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">if</span><span style="color:#666"> </span>(uri.<span style="color:#bbb">getScheme</span>().<span style="color:#bbb">equals</span>(<span style="color:#ed9d13">&#34;jar&#34;</span>))<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#666">          </span><span style="color:#999;font-style:italic">// We have to create a &#34;virtual&#34; filesystem to access the class files stored in the</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span><span style="color:#666">          </span><span style="color:#999;font-style:italic">// .jar file.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">try</span><span style="color:#666"> </span>(FileSystem<span style="color:#666"> </span>fileSystem<span style="color:#666"> </span>=<span style="color:#666"> </span>FileSystems.<span style="color:#bbb">newFileSystem</span>(uri,<span style="color:#666"> </span>Collections.<span style="color:#bbb">emptyMap</span>()))<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#666">              </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span>findClassesInPath(fileSystem.<span style="color:#bbb">getPath</span>(path),<span style="color:#666"> </span>packageName);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#666">          </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span><span style="color:#666">      </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span><span style="color:#666">      </span><span style="color:#999;font-style:italic">// We&#39;re running the injection code from an unpacked archive and can directly access the .class files.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span>findClassesInPath(Paths.<span style="color:#bbb">get</span>(uri),<span style="color:#666"> </span>packageName);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span><span style="color:#666">  </span>}</span></span></code></pre></div>
<p>Therefore, when looking for classes in <code>findClassesInPath</code>, we do not care
if we walk through the archived files of the .jar or are actually looking on
real files on our filesystem. Retrieving the actual class definitions
consists now of just</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// Iterate through all .class files in the given path for the given package.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#666">  </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span>Set&lt;Class&lt;?&gt;&gt;<span style="color:#666"> </span>findClassesInPath(Path<span style="color:#666"> </span>path,<span style="color:#666"> </span>String<span style="color:#666"> </span>packageName)<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">throws</span><span style="color:#666"> </span>IOException<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">try</span><span style="color:#666"> </span>(<span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>walk<span style="color:#666"> </span>=<span style="color:#666"> </span>Files.<span style="color:#bbb">walk</span>(path,<span style="color:#666"> </span>1))<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span>walk<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#666">                  </span>.<span style="color:#bbb">filter</span>(p<span style="color:#666"> </span>-&gt;<span style="color:#666"> </span>!Files.<span style="color:#bbb">isDirectory</span>(p))<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#666">                  </span>.<span style="color:#bbb">filter</span>(p<span style="color:#666"> </span>-&gt;<span style="color:#666"> </span>p.<span style="color:#bbb">toString</span>().<span style="color:#bbb">endsWith</span>(<span style="color:#ed9d13">&#34;.class&#34;</span>))<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span><span style="color:#666">                  </span>.<span style="color:#bbb">map</span>(p<span style="color:#666"> </span>-&gt;<span style="color:#666"> </span>getClass(p,<span style="color:#666"> </span>packageName))<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#666">                  </span>.<span style="color:#bbb">filter</span>(Objects::nonNull)<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span><span style="color:#666">                  </span>.<span style="color:#bbb">collect</span>(Collectors.<span style="color:#bbb">toSet</span>());<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span><span style="color:#666">      </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#666">  </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// Retrieve a class based on the path and package name.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span><span style="color:#666">  </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span>Class&lt;?&gt;<span style="color:#666"> </span>getClass(Path<span style="color:#666"> </span>classPath,<span style="color:#666"> </span>String<span style="color:#666"> </span>packageName)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">try</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span><span style="color:#666">          </span>String<span style="color:#666"> </span>className<span style="color:#666"> </span>=<span style="color:#666"> </span>packageName<span style="color:#666"> </span>+<span style="color:#666"> </span><span style="color:#ed9d13">&#34;.&#34;</span><span style="color:#666"> </span>+<span style="color:#666"> </span>classPath.<span style="color:#bbb">getFileName</span>().<span style="color:#bbb">toString</span>().<span style="color:#bbb">replace</span>(<span style="color:#ed9d13">&#34;.class&#34;</span>,<span style="color:#666"> </span><span style="color:#ed9d13">&#34;&#34;</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span>Class.<span style="color:#bbb">forName</span>(className);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">18</span><span><span style="color:#666">      </span>}<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">catch</span><span style="color:#666"> </span>(ClassNotFoundException<span style="color:#666"> </span>e)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">19</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">null</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">20</span><span><span style="color:#666">      </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">21</span><span><span style="color:#666">  </span>}</span></span></code></pre></div>
<p>I know that we do not recursively descend into subdirectories &ndash; good enough
for the example ;-).</p>
<p>Once we have a list of class definitions, we can finally instantiate them
and call component constructors with the instantiated classes. The dependency
resolution and object instantiation happens in
<code>createSingleton</code>. To simplify our implementation, we have a very basic
dependency resolution. This function is called recursively for all constructor
arguments to find singleton instances. If they are not yet available, we try
to construct them while resolving their dependencies as well. To keep track
of cycles, we keep track of already visited classes.</p>
<p>This could of course be done more clever for the price of blowing up the
number of lines of  code, hence, good enough for this demonstration.</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// Creates a new instance for the passed class using its constructor.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">///</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#666">  </span><span style="color:#999;font-style:italic">/// We resolve all dependent constructor parameters.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#666">  </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">void</span><span style="color:#666"> </span><span style="color:#447fcf">createSingleton</span>(Map&lt;Class&lt;?&gt;,<span style="color:#666"> </span>Object&gt;<span style="color:#666"> </span>singletons,<span style="color:#666"> </span>Set&lt;Class&lt;?&gt;&gt;<span style="color:#666"> </span>visited,<span style="color:#666"> </span>Class&lt;?&gt;<span style="color:#666"> </span>clazz)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#666">      </span><span style="color:#999;font-style:italic">// Cycle detection. We&#39;ve been called to resolve a parameter dependency, but already tried to resolve the</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#666">      </span><span style="color:#999;font-style:italic">// dependencies for this class. When trying to resolve clazz&#39; dependencies, we will run into an infinite cycle.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">if</span><span style="color:#666"> </span>(!visited.<span style="color:#bbb">add</span>(clazz))<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>names<span style="color:#666"> </span>=<span style="color:#666"> </span>visited.<span style="color:#bbb">stream</span>().<span style="color:#bbb">map</span>(Class::getSimpleName).<span style="color:#bbb">collect</span>(Collectors.<span style="color:#bbb">joining</span>(<span style="color:#ed9d13">&#34;, &#34;</span>));<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">throw</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>IllegalStateException(<span style="color:#ed9d13">&#34;Cycle detected. Visited classes=&#34;</span><span style="color:#666"> </span>+<span style="color:#666"> </span>names);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span><span style="color:#666">      </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>cs<span style="color:#666"> </span>=<span style="color:#666"> </span>clazz.<span style="color:#bbb">getDeclaredConstructors</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">if</span><span style="color:#666"> </span>(cs.<span style="color:#bbb">length</span><span style="color:#666"> </span>&gt;<span style="color:#666"> </span>1)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">throw</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>IllegalArgumentException(<span style="color:#ed9d13">&#34;No unique constructor found for &#34;</span><span style="color:#666"> </span>+<span style="color:#666"> </span>clazz.<span style="color:#bbb">getSimpleName</span>());<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span><span style="color:#666">      </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>constructor<span style="color:#666"> </span>=<span style="color:#666"> </span>cs[0];<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>expectedInjections<span style="color:#666"> </span>=<span style="color:#666"> </span>constructor.<span style="color:#bbb">getParameterTypes</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">18</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">19</span><span><span style="color:#666">      </span><span style="color:#999;font-style:italic">// For every dependent dependency, generate a new instance. Note that we implicitly handle the case for</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">20</span><span><span style="color:#666">      </span><span style="color:#999;font-style:italic">// parameter-less constructors here as well.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">21</span><span><span style="color:#666">      </span>Arrays.<span style="color:#bbb">stream</span>(expectedInjections).<span style="color:#bbb">forEach</span>(depClass<span style="color:#666"> </span>-&gt;<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">22</span><span><span style="color:#666">          </span>createSingleton(singletons,<span style="color:#666"> </span>visited,<span style="color:#666"> </span>depClass);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">23</span><span><span style="color:#666">      </span>});<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">24</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">25</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>params<span style="color:#666"> </span>=<span style="color:#666"> </span>Arrays.<span style="color:#bbb">stream</span>(expectedInjections).<span style="color:#bbb">map</span>(singletons::get).<span style="color:#bbb">toArray</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">26</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">try</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">27</span><span><span style="color:#666">          </span>singletons.<span style="color:#bbb">put</span>(clazz,<span style="color:#666"> </span>constructor.<span style="color:#bbb">newInstance</span>(params));<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">28</span><span><span style="color:#666">      </span>}<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">catch</span><span style="color:#666"> </span>(InstantiationException<span style="color:#666"> </span>|<span style="color:#666"> </span>IllegalAccessException<span style="color:#666"> </span>|<span style="color:#666"> </span>InvocationTargetException<span style="color:#666"> </span>e)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">29</span><span><span style="color:#666">          </span><span style="color:#6ab825;font-weight:bold">throw</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>IllegalStateException(<span style="color:#ed9d13">&#34;Unable to create instance for &#34;</span><span style="color:#666"> </span>+<span style="color:#666"> </span>clazz.<span style="color:#bbb">getSimpleName</span>(),<span style="color:#666"> </span>e);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">30</span><span><span style="color:#666">      </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">31</span><span><span style="color:#666">  </span>}</span></span></code></pre></div>
<h2 id="conclusion">Conclusion</h2>
<p>&hellip; and well, that&rsquo;s actually all you need to implement. As stated above,
it&rsquo;s far from complete or error-prone. The important thing, though, is:
dependency injection is not some magical thing that happens behind the
curtains of famous and advanced frameworks. Instead, it&rsquo;s something that <strong>you
could have invented yourself</strong>.</p>
<h2 id="source-code">Source code</h2>
<p>The whole source code can be found
on <a href="https://github.com/mlesniak/dependency-injection">GitHub</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>You could have invented it yourself: Server-Sent Events</title>
      <link>https://mlesniak.com/articles/sse/</link>
      <pubDate>Sat, 21 Dec 2024 00:00:00 +0000</pubDate><author>mail@mlesniak.com (Dr. Michael Lesniak)</author>
      <guid>https://mlesniak.com/articles/sse/</guid>
      <description>&lt;h2 id=&#34;why-do-we-need-server-sent-events&#34;&gt;Why do we need Server-Sent Events?&lt;/h2&gt;
&lt;p&gt;In modern web applications, real-time communication is more
important than ever. Features like live updates, collaborative
editing, notifications, and data streaming have become standard
expectations for users. Before we look at Server-Sent Events,
let&amp;rsquo;s first look at a few alternatives.&lt;/p&gt;
&lt;h3 id=&#34;alternatives-to-sse&#34;&gt;Alternatives to SSE&lt;/h3&gt;
&lt;h4 id=&#34;polling&#34;&gt;Polling&lt;/h4&gt;
&lt;p&gt;Polling is the simplest approach to achieve near-real-time
updates. The client repeatedly sends HTTP requests to the server
at regular intervals to check for changes. While easy to
implement, this method is inefficient. It generates unnecessary
network traffic and puts extra load on the server, especially
when updates are infrequent. The client may poll repeatedly even
when no updates are available, resulting in wasted resources.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h2 id="why-do-we-need-server-sent-events">Why do we need Server-Sent Events?</h2>
<p>In modern web applications, real-time communication is more
important than ever. Features like live updates, collaborative
editing, notifications, and data streaming have become standard
expectations for users. Before we look at Server-Sent Events,
let&rsquo;s first look at a few alternatives.</p>
<h3 id="alternatives-to-sse">Alternatives to SSE</h3>
<h4 id="polling">Polling</h4>
<p>Polling is the simplest approach to achieve near-real-time
updates. The client repeatedly sends HTTP requests to the server
at regular intervals to check for changes. While easy to
implement, this method is inefficient. It generates unnecessary
network traffic and puts extra load on the server, especially
when updates are infrequent. The client may poll repeatedly even
when no updates are available, resulting in wasted resources.</p>
<h4 id="long-polling">Long Polling</h4>
<p>Long polling improves upon basic polling by keeping the HTTP
connection open until the server has an update to send. Once
the server responds with data, the connection closes, and the
client immediately reopens it to wait for the next update. While
this reduces some of the inefficiencies of traditional polling,
it still has drawbacks. Opening and closing connections repeatedly
can strain resources, and implementing long polling often
introduces additional complexity.</p>
<h4 id="websockets">WebSockets</h4>
<p>WebSockets offer full-duplex communication, enabling the server
and client to send messages to each other in real time. While
this is powerful, WebSockets can be overkill for many use cases
that only require one-way communication from the server to the
client. They also add complexity in terms of setup, management,
and maintaining stateful connections, which can be challenging
in distributed or load-balanced environments.</p>
<h3 id="why-sse">Why SSE?</h3>
<p>SSE provide a
lightweight, efficient, and straightforward way for servers to
push updates to clients. They rely on a simple HTTP connection,
making them easy to implement and compatible with most
environments. Unlike WebSockets, SSE is inherently one-directional
(server to client), which is ideal for scenarios like live score
updates, stock price monitoring, or streaming logs.</p>
<p>With SSE, developers get a solution that&rsquo;s simple to use,
resource-friendly, and aligned with the natural statelessness of
HTTP. In many cases, it&rsquo;s the elegant middle ground between the
brute force of polling and the power of WebSockets.</p>
<h2 id="backend-with-spring-boot">Backend with Spring Boot</h2>
<p>The backend streams Server-Sent Events (SSE) using Spring Boot&rsquo;s
<code>SseEmitter</code> class. In general, this should be quite straightforward.
We expose an endpoint via <code>/events</code> with an optional count to restrict the number
of sent events. Once a client (which supports SSE) connects, we emit JSON-serialized
tick events with a unique id and event name.</p>
<p>In a classic REST-based API, one can add this as an extension to the query endpoints.
Besides <code>GET /users</code> and <code>GET /users/{id}</code> you would also have <code>GET /users/events</code> and
<code>GET /users/{id}/events</code>.</p>
<h3 id="implementation">Implementation</h3>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#6ab825;font-weight:bold">record</span> <span style="color:#447fcf;text-decoration:underline">Tick</span>(<span style="color:#6ab825;font-weight:bold">int</span><span style="color:#666"> </span>tick)<span style="color:#666"> </span>{<span style="color:#666"> </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#ffa500">@RestController</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">class</span> <span style="color:#447fcf;text-decoration:underline">EventController</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">final</span><span style="color:#666"> </span>Logger<span style="color:#666"> </span>log<span style="color:#666"> </span>=<span style="color:#666"> </span>LoggerFactory.<span style="color:#bbb">getLogger</span>(EventController.<span style="color:#bbb">class</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">final</span><span style="color:#666"> </span>ObjectMapper<span style="color:#666"> </span>objectMapper;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#447fcf">EventController</span>(ObjectMapper<span style="color:#666"> </span>objectMapper)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span><span style="color:#666">        </span><span style="color:#6ab825;font-weight:bold">this</span>.<span style="color:#bbb">objectMapper</span><span style="color:#666"> </span>=<span style="color:#666"> </span>objectMapper;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span><span style="color:#666">    </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#666">    </span><span style="color:#ffa500">@GetMapping</span>(<span style="color:#ed9d13">&#34;/events&#34;</span>)<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span><span style="color:#666">    </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span>SseEmitter<span style="color:#666"> </span><span style="color:#447fcf">events</span>(<span style="color:#ffa500">@RequestParam</span>(name<span style="color:#666"> </span>=<span style="color:#666"> </span><span style="color:#ed9d13">&#34;count&#34;</span>,<span style="color:#666"> </span>required<span style="color:#666"> </span>=<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">false</span>)<span style="color:#666"> </span>Integer<span style="color:#666"> </span>count)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span><span style="color:#666">        </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>max<span style="color:#666"> </span>=<span style="color:#666"> </span>count<span style="color:#666"> </span>==<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">null</span><span style="color:#666"> </span>?<span style="color:#666"> </span>10<span style="color:#666"> </span>:<span style="color:#666"> </span>count;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span><span style="color:#666">        </span>log.<span style="color:#bbb">info</span>(<span style="color:#ed9d13">&#34;/events called with count {} and max {}&#34;</span>,<span style="color:#666"> </span>count,<span style="color:#666"> </span>max);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span><span style="color:#666">        </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>emitter<span style="color:#666"> </span>=<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>SseEmitter(60<span style="color:#666"> </span>*<span style="color:#666"> </span>1000L);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">18</span><span><span style="color:#666">        </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>id<span style="color:#666"> </span>=<span style="color:#666"> </span>UUID.<span style="color:#bbb">randomUUID</span>().<span style="color:#bbb">toString</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">19</span><span><span style="color:#666">        </span>Executors.<span style="color:#bbb">newSingleThreadScheduledExecutor</span>().<span style="color:#bbb">execute</span>(()<span style="color:#666"> </span>-&gt;<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">20</span><span><span style="color:#666">            </span><span style="color:#6ab825;font-weight:bold">try</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">21</span><span><span style="color:#666">                </span><span style="color:#6ab825;font-weight:bold">for</span><span style="color:#666"> </span>(<span style="color:#6ab825;font-weight:bold">int</span><span style="color:#666"> </span>i<span style="color:#666"> </span>=<span style="color:#666"> </span>0;<span style="color:#666"> </span>i<span style="color:#666"> </span>&lt;<span style="color:#666"> </span>max;<span style="color:#666"> </span>i++)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">22</span><span><span style="color:#666">                    </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>tick<span style="color:#666"> </span>=<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>Tick(i);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">23</span><span><span style="color:#666">                    </span>emitter.<span style="color:#bbb">send</span>(SseEmitter.<span style="color:#bbb">event</span>()<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">24</span><span><span style="color:#666">                            </span>.<span style="color:#bbb">name</span>(<span style="color:#ed9d13">&#34;tick&#34;</span>)<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">25</span><span><span style="color:#666">                            </span>.<span style="color:#bbb">id</span>(id)<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">26</span><span><span style="color:#666">                            </span>.<span style="color:#bbb">data</span>(objectMapper.<span style="color:#bbb">writeValueAsString</span>(tick)));<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">27</span><span><span style="color:#666">                    </span>TimeUnit.<span style="color:#bbb">MILLISECONDS</span>.<span style="color:#bbb">sleep</span>(500);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">28</span><span><span style="color:#666">                </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">29</span><span><span style="color:#666">                </span><span style="color:#999;font-style:italic">// Without a payload, the event is not correctly processed</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">30</span><span><span style="color:#666">                </span><span style="color:#999;font-style:italic">// in the browser. This is actually expected behaviour,</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">31</span><span><span style="color:#666">                </span><span style="color:#999;font-style:italic">// see https://github.com/denoland/deno/issues/23135.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">32</span><span><span style="color:#666">                </span>emitter.<span style="color:#bbb">send</span>(SseEmitter.<span style="color:#bbb">event</span>().<span style="color:#bbb">name</span>(<span style="color:#ed9d13">&#34;close&#34;</span>).<span style="color:#bbb">data</span>(<span style="color:#ed9d13">&#34;&#34;</span>));<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">33</span><span><span style="color:#666">                </span>log.<span style="color:#bbb">info</span>(<span style="color:#ed9d13">&#34;Closing connection&#34;</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">34</span><span><span style="color:#666">                </span>emitter.<span style="color:#bbb">complete</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">35</span><span><span style="color:#666">            </span>}<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">catch</span><span style="color:#666"> </span>(Exception<span style="color:#666"> </span>e)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">36</span><span><span style="color:#666">                </span>emitter.<span style="color:#bbb">completeWithError</span>(e);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">37</span><span><span style="color:#666">            </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">38</span><span><span style="color:#666">        </span>});<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">39</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">40</span><span><span style="color:#666">        </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span>emitter;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">41</span><span><span style="color:#666">    </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">42</span><span>}</span></span></code></pre></div>
<p>The only annoying trap was figuring out why the <code>close</code> event
was never processed by the frontend (see below). If you omit
any data, the browser is free to ignore the event&hellip;</p>
<h3 id="testing-the-endpoint-with-curl">Testing the endpoint with curl</h3>
<p>When using curl, this looks like</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span>$ curl http://localhost:9000/events?count=<span style="color:#3677a9">4</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span>event:tick
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span>id:ba711ed1-bb55-4696-9312-ca4c10725e5a
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span>data:{<span style="color:#ed9d13">&#34;tick&#34;</span>:0}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span>event:tick
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span>id:ba711ed1-bb55-4696-9312-ca4c10725e5a
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span>data:{<span style="color:#ed9d13">&#34;tick&#34;</span>:1}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span>event:tick
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span>id:ba711ed1-bb55-4696-9312-ca4c10725e5a
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span>data:{<span style="color:#ed9d13">&#34;tick&#34;</span>:2}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span>event:tick
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span>id:ba711ed1-bb55-4696-9312-ca4c10725e5a
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span>data:{<span style="color:#ed9d13">&#34;tick&#34;</span>:3}
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">18</span><span>event:close
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">19</span><span>data:</span></span></code></pre></div>
<h2 id="a-simple-user-interface">A simple user interface</h2>
<p>By any means, I am not a frontend software engineer. Nonetheless, here&rsquo;s a basic, framework-less UI which
shows how to use SSE in a client.
<img src="https://mlesniak.com/articles/sse.png" />
The key implementation is this JavaScript snippet</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-javascript" data-lang="javascript"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#24909d">document</span>.querySelector(<span style="color:#ed9d13">&#39;form&#39;</span>).addEventListener(<span style="color:#ed9d13">&#39;submit&#39;</span>, e =&gt; {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span>    e.preventDefault();
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span>    <span style="color:#6ab825;font-weight:bold">const</span> count = <span style="color:#24909d">document</span>.getElementById(<span style="color:#ed9d13">&#39;count&#39;</span>).value;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span>    <span style="color:#24909d">document</span>.getElementById(<span style="color:#ed9d13">&#39;count&#39;</span>).value = <span style="color:#ed9d13">&#39;&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span>    <span style="color:#6ab825;font-weight:bold">const</span> eventDisplay = <span style="color:#24909d">document</span>.getElementById(<span style="color:#ed9d13">&#39;events&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span>    eventDisplay.innerHTML = <span style="color:#ed9d13">&#39;&#39;</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span>    <span style="color:#6ab825;font-weight:bold">if</span> (<span style="color:#24909d">window</span>.eventSource) <span style="color:#24909d">window</span>.eventSource.close();
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span>    <span style="color:#24909d">window</span>.eventSource = <span style="color:#6ab825;font-weight:bold">new</span> EventSource(<span style="color:#ed9d13">`/events?count=</span><span style="color:#ed9d13">${</span>count<span style="color:#ed9d13">}</span><span style="color:#ed9d13">`</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span>    eventSource.addEventListener(<span style="color:#ed9d13">&#39;tick&#39;</span>, event =&gt; {
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span>        <span style="color:#6ab825;font-weight:bold">const</span> p = <span style="color:#24909d">document</span>.createElement(<span style="color:#ed9d13">&#39;p&#39;</span>);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span>        p.textContent = <span style="color:#ed9d13">`Tick: </span><span style="color:#ed9d13">${</span>JSON.parse(event.data).tick + <span style="color:#3677a9">1</span><span style="color:#ed9d13">}</span><span style="color:#ed9d13">`</span>;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span>        eventDisplay.appendChild(p);
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span>    });
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span>    eventSource.addEventListener(<span style="color:#ed9d13">&#39;close&#39;</span>, () =&gt; eventSource.close());
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">18</span><span>});</span></span></code></pre></div>
<p>Once we&rsquo;ve subscribed to the <code>submit</code> event of the form, we use the
Browser&rsquo;s <a href="https://developer.mozilla.org/de/docs/Web/API/EventSource">EventSource API</a> to listen
to events sent by the server and react accordingly.</p>
<p>If we do not react to a <code>close</code> event by closing our listener, and the server closes its connection on their side,
the browser will simply reopen the connection after a few seconds and listen to new events again.</p>
<h2 id="the-specification">The specification</h2>
<p>Server-Sent Events (SSE) are defined as <a href="https://html.spec.whatwg.org/multipage/server-sent-events.html">part of the HTML5 specification</a>.
They provide a standardized way for servers to
push updates to web clients over a persistent HTTP connection
using a lightweight, text-based protocol.</p>
<h3 id="key-characteristics">Key Characteristics</h3>
<ol>
<li>
<p><strong>Protocol</strong>:</p>
<ul>
<li>SSE uses a standard HTTP connection (typically GET requests).</li>
<li>The <code>Content-Type</code> for SSE responses must be <code>text/event-stream</code>.</li>
<li>HTTP headers like <code>Cache-Control: no-cache</code> are commonly used
to prevent caching of the event stream by intermediaries.</li>
<li>Connections are usually kept open indefinitely, relying on
persistent HTTP/1.1 or HTTP/2 features.</li>
</ul>
</li>
<li>
<p><strong>Event Format</strong>:</p>
<ul>
<li>Events are sent as plain text in the following structure:






<pre tabindex="0"><code>id: &lt;unique-id&gt;
event: &lt;event-name&gt;
data: &lt;payload&gt;</code></pre>
</li>
<li>Each event ends with a blank line to indicate completion.</li>
<li>Binary data is not directly supported, i.e., must be encoded with Base64.</li>
</ul>
</li>
<li>
<p><strong>Auto-Reconnection</strong>:</p>
<ul>
<li>If the connection is lost, the browser automatically attempts
to reconnect after a short delay.</li>
<li>The <code>id</code> field allows clients to resume from the last event
by sending a <code>Last-Event-ID</code> header in subsequent requests.</li>
</ul>
</li>
<li>
<p><strong>Character Encoding</strong>:</p>
<ul>
<li>SSE requires UTF-8 encoding for all transmitted data.</li>
</ul>
</li>
</ol>
<h3 id="an-exchange-with-curl">An exchange with curl</h3>
<p>Coming back to our example with curl, we can verify the actual
HTTP headers sent in the response.</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span>$ curl -v http://localhost:9000/events?count=<span style="color:#3677a9">4</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span>* Host localhost:9000 was resolved.
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span>* IPv6: ::1
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span>* IPv4: 127.0.0.1
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span>*   Trying [::1]:9000...
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span>* Connected to localhost (::1) port <span style="color:#3677a9">9000</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span>&gt; GET /events?count=<span style="color:#3677a9">4</span> HTTP/1.1
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span>&gt; Host: localhost:9000
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span>&gt; User-Agent: curl/8.9.1
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span>&gt; Accept: */*
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span>&gt;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span>* Request completely sent off
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span>&lt; HTTP/1.1 <span style="color:#3677a9">200</span>
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span>&lt; Content-Type: text/event-stream
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span>&lt; Transfer-Encoding: chunked
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span>&lt; Date: Sat, <span style="color:#3677a9">21</span> Dec <span style="color:#3677a9">2024</span> 17:32:27 GMT
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span>&lt;
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">18</span><span>event:tick
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">19</span><span>id:5d482700-187c-4928-8de5-cca0681f0aac
</span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">20</span><span>data:{<span style="color:#ed9d13">&#34;tick&#34;</span>:0}</span></span></code></pre></div>
<h2 id="a-backend-using-just-the-jdk">A backend using just the JDK</h2>
<p>Looks like Server-Sent Events are not magic at all. While Spring allows us to use
them via a comfortable API through the <code>SseEmitter</code> class, we can easily write our
own backend implementation &ndash; the frontend part is left as an exercise for the reader.</p>
<p>For convinience, we also left out handling of the optional <code>count</code> parameter since
it&rsquo;s not relevant for the protocol and the thread handling. It&rsquo;s easy to add but
would hide the relevant part, the implementation of SSE itself.</p>






<div class="highlight"><pre tabindex="0" style="color:#d0d0d0;background-color:#202020;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-java" data-lang="java"><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 1</span><span><span style="color:#999;font-style:italic">// ...boring package declaration and imports...</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 2</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 3</span><span><span style="color:#6ab825;font-weight:bold">record</span> <span style="color:#447fcf;text-decoration:underline">TickEvent</span>(<span style="color:#6ab825;font-weight:bold">int</span><span style="color:#666"> </span>tick)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 4</span><span><span style="color:#666">   </span>String<span style="color:#666"> </span><span style="color:#447fcf">toJson</span>()<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 5</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span><span style="color:#ed9d13">&#34;{\&#34;tick\&#34;:&#34;</span><span style="color:#666"> </span>+<span style="color:#666"> </span>tick<span style="color:#666"> </span>+<span style="color:#666"> </span><span style="color:#ed9d13">&#34;}&#34;</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 6</span><span><span style="color:#666">   </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 7</span><span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 8</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868"> 9</span><span><span style="color:#6ab825;font-weight:bold">record</span> <span style="color:#447fcf;text-decoration:underline">SseEvent</span>(String<span style="color:#666"> </span>id,<span style="color:#666"> </span>String<span style="color:#666"> </span>event,<span style="color:#666"> </span>String<span style="color:#666"> </span>data)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">10</span><span><span style="color:#666">   </span>String<span style="color:#666"> </span><span style="color:#447fcf">toSseFormat</span>()<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">11</span><span><span style="color:#666">      </span><span style="color:#999;font-style:italic">// Not beautiful, but good enough.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">12</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">return</span><span style="color:#666"> </span><span style="color:#ed9d13">&#34;id: &#34;</span><span style="color:#666"> </span>+<span style="color:#666"> </span>id<span style="color:#666"> </span>+<span style="color:#666"> </span><span style="color:#ed9d13">&#34;\n&#34;</span><span style="color:#666"> </span>+<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">13</span><span><span style="color:#666">              </span><span style="color:#ed9d13">&#34;event: &#34;</span><span style="color:#666"> </span>+<span style="color:#666"> </span>event<span style="color:#666"> </span>+<span style="color:#666"> </span><span style="color:#ed9d13">&#34;\n&#34;</span><span style="color:#666"> </span>+<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">14</span><span><span style="color:#666">              </span><span style="color:#ed9d13">&#34;data: &#34;</span><span style="color:#666"> </span>+<span style="color:#666"> </span>data<span style="color:#666"> </span>+<span style="color:#666"> </span><span style="color:#ed9d13">&#34;\n\n&#34;</span>;<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">15</span><span><span style="color:#666">   </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">16</span><span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">17</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">18</span><span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">class</span> <span style="color:#447fcf;text-decoration:underline">SseServer</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">19</span><span><span style="color:#666">   </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">void</span><span style="color:#666"> </span><span style="color:#447fcf">main</span>(String[]<span style="color:#666"> </span>args)<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">throws</span><span style="color:#666"> </span>Exception<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">20</span><span><span style="color:#666">      </span><span style="color:#999;font-style:italic">// Create a basic HTTP server which is part of the JDK.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">21</span><span><span style="color:#666">      </span>HttpServer<span style="color:#666"> </span>server<span style="color:#666"> </span>=<span style="color:#666"> </span>HttpServer.<span style="color:#bbb">create</span>(<span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>InetSocketAddress(9000),<span style="color:#666"> </span>0);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">22</span><span><span style="color:#666">      </span>server.<span style="color:#bbb">createContext</span>(<span style="color:#ed9d13">&#34;/events&#34;</span>,<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>SseHandler());<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">23</span><span><span style="color:#666">      </span>server.<span style="color:#bbb">setExecutor</span>(Executors.<span style="color:#bbb">newCachedThreadPool</span>());<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">24</span><span><span style="color:#666">      </span>server.<span style="color:#bbb">start</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">25</span><span><span style="color:#666">   </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">26</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">27</span><span><span style="color:#666">   </span><span style="color:#6ab825;font-weight:bold">static</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">class</span> <span style="color:#447fcf;text-decoration:underline">SseHandler</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">implements</span><span style="color:#666"> </span>HttpHandler<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">28</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">private</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">final</span><span style="color:#666"> </span>ObjectMapper<span style="color:#666"> </span>objectMapper<span style="color:#666"> </span>=<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>ObjectMapper();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">29</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">30</span><span><span style="color:#666">      </span><span style="color:#ffa500">@Override</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">31</span><span><span style="color:#666">      </span><span style="color:#6ab825;font-weight:bold">public</span><span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">void</span><span style="color:#666"> </span><span style="color:#447fcf">handle</span>(HttpExchange<span style="color:#666"> </span>exchange)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">32</span><span><span style="color:#666">         </span><span style="color:#6ab825;font-weight:bold">try</span><span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">33</span><span><span style="color:#666">            </span><span style="color:#999;font-style:italic">// Set default headers for SSE.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">34</span><span><span style="color:#666">            </span>exchange.<span style="color:#bbb">getResponseHeaders</span>().<span style="color:#bbb">set</span>(<span style="color:#ed9d13">&#34;Content-Type&#34;</span>,<span style="color:#666"> </span><span style="color:#ed9d13">&#34;text/event-stream&#34;</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">35</span><span><span style="color:#666">            </span>exchange.<span style="color:#bbb">getResponseHeaders</span>().<span style="color:#bbb">set</span>(<span style="color:#ed9d13">&#34;Cache-Control&#34;</span>,<span style="color:#666"> </span><span style="color:#ed9d13">&#34;no-cache&#34;</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">36</span><span><span style="color:#666">            </span>exchange.<span style="color:#bbb">getResponseHeaders</span>().<span style="color:#bbb">set</span>(<span style="color:#ed9d13">&#34;Connection&#34;</span>,<span style="color:#666"> </span><span style="color:#ed9d13">&#34;keep-alive&#34;</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">37</span><span><span style="color:#666">            </span>exchange.<span style="color:#bbb">sendResponseHeaders</span>(200,<span style="color:#666"> </span>0);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">38</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">39</span><span><span style="color:#666">            </span><span style="color:#6ab825;font-weight:bold">try</span><span style="color:#666"> </span>(OutputStream<span style="color:#666"> </span>os<span style="color:#666"> </span>=<span style="color:#666"> </span>exchange.<span style="color:#bbb">getResponseBody</span>())<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">40</span><span><span style="color:#666">               </span><span style="color:#6ab825;font-weight:bold">var</span><span style="color:#666"> </span>id<span style="color:#666"> </span>=<span style="color:#666"> </span>UUID.<span style="color:#bbb">randomUUID</span>().<span style="color:#bbb">toString</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">41</span><span><span style="color:#666">               </span><span style="color:#6ab825;font-weight:bold">for</span><span style="color:#666"> </span>(<span style="color:#6ab825;font-weight:bold">int</span><span style="color:#666"> </span>i<span style="color:#666"> </span>=<span style="color:#666"> </span>0;<span style="color:#666"> </span>i<span style="color:#666"> </span>&lt;<span style="color:#666"> </span>10;<span style="color:#666"> </span>i++)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">42</span><span><span style="color:#666">                  </span>TickEvent<span style="color:#666"> </span>tickEvent<span style="color:#666"> </span>=<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>TickEvent(i);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">43</span><span><span style="color:#666">                  </span>SseEvent<span style="color:#666"> </span>sseEvent<span style="color:#666"> </span>=<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>SseEvent(<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">44</span><span><span style="color:#666">                          </span>id,<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">45</span><span><span style="color:#666">                          </span><span style="color:#ed9d13">&#34;tick&#34;</span>,<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">46</span><span><span style="color:#666">                          </span>tickEvent.<span style="color:#bbb">toJson</span>()<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">47</span><span><span style="color:#666">                  </span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">48</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">49</span><span><span style="color:#666">                  </span>os.<span style="color:#bbb">write</span>(sseEvent.<span style="color:#bbb">toSseFormat</span>().<span style="color:#bbb">getBytes</span>());<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">50</span><span><span style="color:#666">                  </span>os.<span style="color:#bbb">flush</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">51</span><span><span style="color:#666">                  </span>Thread.<span style="color:#bbb">sleep</span>(500);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">52</span><span><span style="color:#666">               </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">53</span><span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">54</span><span><span style="color:#666">               </span>SseEvent<span style="color:#666"> </span>closeEvent<span style="color:#666"> </span>=<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">new</span><span style="color:#666"> </span>SseEvent(<span style="color:#ed9d13">&#34;&#34;</span>,<span style="color:#666"> </span><span style="color:#ed9d13">&#34;close&#34;</span>,<span style="color:#666"> </span><span style="color:#ed9d13">&#34;&#34;</span>);<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">55</span><span><span style="color:#666">               </span>os.<span style="color:#bbb">write</span>(closeEvent.<span style="color:#bbb">toSseFormat</span>().<span style="color:#bbb">getBytes</span>());<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">56</span><span><span style="color:#666">               </span>os.<span style="color:#bbb">flush</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">57</span><span><span style="color:#666">            </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">58</span><span><span style="color:#666">         </span>}<span style="color:#666"> </span><span style="color:#6ab825;font-weight:bold">catch</span><span style="color:#666"> </span>(Exception<span style="color:#666"> </span>e)<span style="color:#666"> </span>{<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">59</span><span><span style="color:#666">            </span><span style="color:#999;font-style:italic">// 🙈 ... good enough for us.</span><span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">60</span><span><span style="color:#666">            </span>e.<span style="color:#bbb">printStackTrace</span>();<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">61</span><span><span style="color:#666">         </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">62</span><span><span style="color:#666">      </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">63</span><span><span style="color:#666">   </span>}<span style="color:#666">
</span></span></span><span style="display:flex;"><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#686868">64</span><span>}</span></span></code></pre></div>
<h2 id="source-code">Source code</h2>
<p>The whole source code can be found on <a href="https://github.com/mlesniak/server-side-events">GitHub</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Contact options and legal notice</title>
      <link>https://mlesniak.com/contact/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>mail@mlesniak.com (Dr. Michael Lesniak)</author>
      <guid>https://mlesniak.com/contact/</guid>
      <description>&lt;h4 id=&#34;provider&#34;&gt;Provider&lt;/h4&gt;
&lt;p&gt;Dr. Michael Lesniak&lt;br&gt;
Joseph-König-Str. 8&lt;br&gt;
48147 Münster&lt;br&gt;&lt;/p&gt;
&lt;h4 id=&#34;contact-options&#34;&gt;Contact options&lt;/h4&gt;
&lt;p&gt;Email: &lt;a href=&#34;mailto:mail@mlesniak.com&#34;&gt;mail@mlesniak.com&lt;/a&gt;  &lt;br/&gt;
LinkedIn: &lt;a href=&#34;https://www.linkedin.com/in/dr-michael-lesniak-1577a315/&#34;&gt;Link to my profile&lt;/a&gt; &lt;br/&gt;
Phone: +49 151 551 69 221&lt;br/&gt;&lt;/p&gt;
&lt;h4 id=&#34;company-details&#34;&gt;Company details&lt;/h4&gt;
&lt;p&gt;VAT identification number: DE366616834&lt;/p&gt;
&lt;h4 id=&#34;responsible-for-content&#34;&gt;Responsible for content&lt;/h4&gt;
&lt;p&gt;Responsible for content pursuant to § 18 (2) MStV: Dr. Michael Lesniak, Joseph-König-Str. 8, 48147 Münster&lt;/p&gt;
&lt;h4 id=&#34;liability-and-intellectual-property-rights-information&#34;&gt;Liability and Intellectual Property Rights Information&lt;/h4&gt;
&lt;p class=&#34;justify&#34;&gt;Liability Disclaimer: While the content of this website has been put together with great care and reflects our current knowledge, it is provided for information purposes without being legally binding, unless the disclosure of this information is required by law (e.g. the legal information, the privacy policy, terms and conditions or mandatory instructions for consumers). We reserve the right to modify or delete the content, whether in full or in part, provided this does not affect our existing contractual obligations. All website content is subject to change and non-binding.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<h4 id="provider">Provider</h4>
<p>Dr. Michael Lesniak<br>
Joseph-König-Str. 8<br>
48147 Münster<br></p>
<h4 id="contact-options">Contact options</h4>
<p>Email: <a href="mailto:mail@mlesniak.com">mail@mlesniak.com</a>  <br/>
LinkedIn: <a href="https://www.linkedin.com/in/dr-michael-lesniak-1577a315/">Link to my profile</a> <br/>
Phone: +49 151 551 69 221<br/></p>
<h4 id="company-details">Company details</h4>
<p>VAT identification number: DE366616834</p>
<h4 id="responsible-for-content">Responsible for content</h4>
<p>Responsible for content pursuant to § 18 (2) MStV: Dr. Michael Lesniak, Joseph-König-Str. 8, 48147 Münster</p>
<h4 id="liability-and-intellectual-property-rights-information">Liability and Intellectual Property Rights Information</h4>
<p class="justify">Liability Disclaimer: While the content of this website has been put together with great care and reflects our current knowledge, it is provided for information purposes without being legally binding, unless the disclosure of this information is required by law (e.g. the legal information, the privacy policy, terms and conditions or mandatory instructions for consumers). We reserve the right to modify or delete the content, whether in full or in part, provided this does not affect our existing contractual obligations. All website content is subject to change and non-binding.</p>
<p class="justify">Link Disclaimer: We do not accept any responsibility for or endorse the content of external websites we link to, whether directly or indirectly. The providers of the linked websites are solely responsible for all content presented on their websites and in particular, any damage resulting from the use of the information offered on their websites.</p>
<p class="justify">Copyrights and Trademarks: All contents presented on this website, such as texts, photographs, graphics, brands and trademarks are protected by the respective intellectual property rights (copyrights, trademark rights). The use, reproduction, etc. are subject to our rights or the rights of the respective authors or rights owners.</p>
<p class="justify">Information on legal infringements: Please notify us if you notice any rights violations on our website. Once notified, we will promptly remove any illegal content or links.</p>
<h4 id="privacy-policy">Privacy Policy</h4>
<p class="justify"><strong>Controller</strong> — Personal data on this website is processed by the provider named above (see &ldquo;Provider&rdquo;).</p>
<p class="justify"><strong>Hosting</strong> — This website is hosted on our own server. The underlying infrastructure (data center and physical server) is provided by Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Germany. The server is located in Germany (EU). The web server does not keep access logs: no IP addresses or request data are written to log files. Your IP address is processed transiently by the server and network stack only to deliver the requested pages, based on our legitimate interest in operating the site securely (Art. 6(1)(f) GDPR). An order-processing agreement (AVV, Art. 28 GDPR) is in place with the infrastructure provider.</p>
<p class="justify"><strong>Contacting us</strong> — If you contact us by email, the data you provide is processed solely to handle your enquiry (Art. 6(1)(b)/(f) GDPR) and deleted once it is resolved, subject to statutory retention periods.</p>
<p class="justify"><strong>External links</strong> — This site links to third-party services (Calendly, LinkedIn, Lichess). No data is sent to them when you load this site — only once you click a link and leave. Their own privacy policies then apply; Calendly in particular is US-based.</p>
<p class="justify"><strong>Your rights</strong> — You have the rights to access (Art. 15), rectification (Art. 16), erasure (Art. 17), restriction (Art. 18), data portability (Art. 20), and objection (Art. 21). Where processing relies on consent, you may withdraw it at any time. You may also lodge a complaint with a supervisory authority; the one responsible for our location is the LDI NRW (Landesbeauftragte für Datenschutz und Informationsfreiheit Nordrhein-Westfalen).</p>
<p class="justify"><strong>Encryption</strong> — All connections to this site use TLS/SSL.</p>
<p class="justify"><em>As of June 2026.</em></p>
]]></content:encoded>
    </item>
    <item>
      <title>My Journey</title>
      <link>https://mlesniak.com/journey/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>mail@mlesniak.com (Dr. Michael Lesniak)</author>
      <guid>https://mlesniak.com/journey/</guid>
      <description>&lt;p&gt;I’ve let my younger self down.&lt;/p&gt;
&lt;p&gt;I started coding on a C64 and achieved a lot since then.&lt;/p&gt;
&lt;p&gt;Leading teams. Designing systems. Solving tough bugs. Earning promotions.&lt;/p&gt;
&lt;p&gt;But I felt empty.&lt;/p&gt;
&lt;p&gt;Even though my CV looked impressive, I didn&amp;rsquo;t feel connected to what I loved.&lt;/p&gt;
&lt;p&gt;Getting my hands dirty, solving real problems that make a difference for customers.&lt;/p&gt;
&lt;p&gt;To bring back the spark and make my younger self proud again, I moved to freelancing.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I’ve let my younger self down.</p>
<p>I started coding on a C64 and achieved a lot since then.</p>
<p>Leading teams. Designing systems. Solving tough bugs. Earning promotions.</p>
<p>But I felt empty.</p>
<p>Even though my CV looked impressive, I didn&rsquo;t feel connected to what I loved.</p>
<p>Getting my hands dirty, solving real problems that make a difference for customers.</p>
<p>To bring back the spark and make my younger self proud again, I moved to freelancing.</p>
<p>I build impactful features — from understanding the goal, to architecture, implementation, and rollout.</p>
<p>The spark returned.</p>
<p>My younger self is proud again, though would have a lot to say about my lack of gaming skills.</p>
<p>I want my work to matter.</p>
<p>That’s why I focus on startups and small businesses.</p>
<p>They don’t need consulting, they need to move fast.</p>
<p>They need someone who delivers immediate value.</p>
<p>That’s where I excel.</p>
<p>What’s in it for you?</p>
<p>I improve your architecture to handle 10x the data, build a time-critical feature to close a big deal, or fix a framework bug which blocks a critical security upgrade.</p>
<p>I don’t just offer advice.</p>
<p>I’m in the code with your team.</p>
<p>I build what helps you scale and succeed.</p>
<p>Yet, I’m not the right fit for every company.</p>
<p>Don’t reach out if you’re</p>
<ul>
<li>
<p>looking for consultants rather than doers.</p>
</li>
<li>
<p>not ready for rapid, hands-on problem-solving.</p>
</li>
<li>
<p>preferring slow processes over fast, effective execution.</p>
</li>
</ul>
<p>Ready for someone who doesn’t just write code but crafts solutions that resonate with your mission &hellip; and has a talent for confusing, but funny, metaphors?</p>
<p>Shoot me a <a href="mailto:mail@mlesniak.com">message</a>. I’ll respond in &lt;10ms.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Qualifications, Past Projects and Education</title>
      <link>https://mlesniak.com/resume/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>mail@mlesniak.com (Dr. Michael Lesniak)</author>
      <guid>https://mlesniak.com/resume/</guid>
      <description>&lt;p class=&#34;availability&#34;&gt;Available from July 2026. &lt;a href=&#34;mailto:mail@mlesniak.com&#34;&gt;Get in touch.&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&#34;qualifications&#34;&gt;Qualifications&lt;/h4&gt;
&lt;h5 id=&#34;expert&#34;&gt;Expert&lt;/h5&gt;
&lt;p&gt;Java, Kotlin, Spring Boot, Docker, Cloud (AWS), Terraform, Git, SQL,
Software architecture, Distributed systems, Microservices, API Design, REST,
Scrum, Code Reviews, Refactoring, Requirements Engineering,
Implementation, Debugging, Analytical thinking, Leadership, Mentoring and coaching&lt;/p&gt;
&lt;h5 id=&#34;professional&#34;&gt;Professional&lt;/h5&gt;
&lt;p&gt;Linux,
Domain-Driven Design, Serverless,
MongoDB, Cassandra, Neo4J, PostgreSQL,
RAG, LLM, Claude Code,
Project management, Testing, Implementation and analysis of algorithms, Atlassian products (JIRA, Confluence)&lt;/p&gt;
&lt;h5 id=&#34;basic&#34;&gt;Basic&lt;/h5&gt;
&lt;p&gt;Go, JavaScript, TypeScript, Python, React, Angular, gRPC, Cloud (Azure, GCP), MCP&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p class="availability">Available from July 2026. <a href="mailto:mail@mlesniak.com">Get in touch.</a></p>
<h4 id="qualifications">Qualifications</h4>
<h5 id="expert">Expert</h5>
<p>Java, Kotlin, Spring Boot, Docker, Cloud (AWS), Terraform, Git, SQL,
Software architecture, Distributed systems, Microservices, API Design, REST,
Scrum, Code Reviews, Refactoring, Requirements Engineering,
Implementation, Debugging, Analytical thinking, Leadership, Mentoring and coaching</p>
<h5 id="professional">Professional</h5>
<p>Linux,
Domain-Driven Design, Serverless,
MongoDB, Cassandra, Neo4J, PostgreSQL,
RAG, LLM, Claude Code,
Project management, Testing, Implementation and analysis of algorithms, Atlassian products (JIRA, Confluence)</p>
<h5 id="basic">Basic</h5>
<p>Go, JavaScript, TypeScript, Python, React, Angular, gRPC, Cloud (Azure, GCP), MCP</p>
<h4 id="languages">Languages</h4>
<ul>
<li>German: native speaker</li>
<li>English: professional proficiency</li>
</ul>
<h4 id="certifications">Certifications</h4>
<ul>
<li><strong>05/2026</strong>  AWS Certified AI Practitioner</li>
<li><strong>05/2026</strong>  Generative AI with Large Language Models</li>
<li><strong>09/2024</strong>  AWS Certified Solution Architect Associate</li>
<li><strong>11/2023</strong>  HashiCorp Certified: Terraform Associate (003)</li>
<li><strong>03/2021</strong>  AWS Certified Cloud Practitioner</li>
<li><strong>02/2019 – 04/2021</strong>  Training as a Systemic Coach (SG)
<ul>
<li>Supported and guided individuals and teams in developing solutions and overcoming conflicts through systemic interventions</li>
<li>Assisted with change processes for teams and organizations to enable smooth transitions and effective adaptation</li>
</ul>
</li>
<li><strong>10/2018</strong>  Certified Neo4J Professional</li>
<li><strong>03/2017</strong>  Scrum Professional Scrum Master (ITEMO)</li>
<li><strong>11/2014</strong>  MapR Certified Hadoop Professional: Developer (MCHP:D)</li>
<li><strong>02/2013</strong>  Certified Professional for Requirements Engineering Foundation Level</li>
</ul>
<h4 id="project-list">Project list</h4>
<h5 id="042026--technical-advisor">04/2026 | Technical Advisor</h5>
<p><a href="https://mach.today/">MACH</a></p>
<ul>
<li>Advised on technical architecture and platform strategy for an early-stage business initiative</li>
<li>Evaluated infrastructure options, cost structures, and scalability tradeoffs</li>
<li>Contributed to product and system design decisions during the concept phase</li>
</ul>
<h5 id="102025--042026--senior-java-software-engineer">10/2025 &ndash; 04/2026 | Senior Java Software Engineer</h5>
<p><a href="https://www.micromerce.com">Micromerce</a></p>
<ul>
<li>Built and evolved a data lake to centralize data processing, analytics, and long-term storage, improving data transparency and enabling better business decisions</li>
<li>Designed and implemented an S3-based backup and archiving strategy, increasing data reliability while significantly optimizing storage costs through lifecycle management</li>
<li>Developed a comprehensive data deletion and retention concept, ensuring GDPR-compliant data handling and reducing operational and legal risks</li>
<li>Delivered targeted performance and cost optimizations, reducing execution times, infrastructure costs, and system load while improving overall stability</li>
<li>Analyzed and resolved production issues and bottlenecks, leading to increased system reliability and availability</li>
<li>Established scalable data processing and background job workflows to handle large data volumes efficiently</li>
<li>Improved monitoring, logging, and alerting, enabling faster incident detection and reduced downtime</li>
<li>Enhanced deployment and quality assurance processes, increasing release reliability and development efficiency</li>
</ul>
<p>Keywords: Java (17, 21), AWS, SQS, DynamoDB, Microservices, CDK, TypeScript</p>
<h5 id="122024--092025--senior-software-engineer">12/2024 &ndash; 09/2025 | Senior Software Engineer</h5>
<p><a href="https://milia.io">Milia</a></p>
<ul>
<li>Enabled fulltext search by designing an OpenSearch (ElasticSearch)-based solution and incorporating business-specific rules</li>
<li>Improved client responsiveness by designing and implementing an adaptive invitation reminder subsystem</li>
<li>Improved client experience and reduced support team load by analyzing, designing, implementing, and maintaining a DATEV document status replication system</li>
<li>Improved platform scalability by designing and re-implementing synchronous processes as asynchronous</li>
<li>Analyzed and fixed bugs in close collaboration with the support team</li>
<li>Improved the CI/CD GitHub-based build pipeline</li>
<li>Designed and implemented business-specific features</li>
</ul>
<p>Keywords: Java, AWS, SQS, Postgresql, Microservices, DATEV</p>
<h5 id="022024--082024--senior-java-software-engineer">02/2024 &ndash; 08/2024 | Senior Java Software Engineer</h5>
<p><a href="https://www.micromerce.com">Micromerce</a></p>
<ul>
<li>Improved ticket processing time by connecting the ticket system to the OpenAI API</li>
<li>Enhanced AI responses by setting up and configuring a RAG-based system</li>
<li>Reduced database costs in DynamoDB by over 30% through improvements to the company-specific persistence framework</li>
<li>Enabled teams to work faster by adding JavaScript support alongside Groovy in the in-house scripting engine</li>
<li>Made solutions engineers more productive by developing a custom VSCode extension supporting an internal configuration language with validation and auto-completion</li>
<li>Improved platform stability by analyzing and fixing SQS performance issues</li>
<li>Future-proofed the infrastructure-as-code setup by adopting CDK for AWS and migrating the existing codebase from an unsupported legacy solution</li>
<li>Improved platform security by implementing a custom brute-force prevention mechanism</li>
<li>Helped teams evaluate tradeoffs of different architectural approaches</li>
</ul>
<p>Keywords: Java (17, 21), AWS, SQS, DynamoDB, Microservices, CDK, TypeScript</p>
<h5 id="012024---022024--consultant">01/2024 - 02/2024 | Consultant</h5>
<p><a href="https://www.linkedin.com/company/algominds/">AlgoMinds GmbH</a></p>
<ul>
<li>Implemented a complete CI pipeline (including supporting systems such as build pools) from scratch via Infrastructure as Code on Azure</li>
</ul>
<p>Keywords: Infrastructure as Code, Pulumi, Microsoft Azure, C#, .NET Core 8, TypeScript</p>
<!-- #####  04/2021 – 11/2023 | Senior, Staff and Principal Engineer  -->
<h5 id="042021--112023--principal-engineer">04/2021 – 11/2023 | Principal Engineer</h5>
<p><a href="https://de.scalable.capital/en">Scalable Capital GmbH</a></p>
<ul>
<li>Planned, led, and implemented a large-scale migration of a <strong>mission-critical retail investment platform</strong> (400k+ lines of code) from Spring Boot 2.3 to 3.1</li>
<li>Designed and prototyped refactoring approaches from a monolithic to a distributed service-oriented architecture</li>
<li>Defined and introduced company-wide standards and best practices including Architectural Decision Records, code review processes, and service templates</li>
<li>Planned, designed, coordinated, and implemented business-critical product features</li>
<li>Drove company-wide technical decisions and architectural designs, adopted across engineering leadership</li>
</ul>
<p>Keywords: Kotlin, Java, Go, Python, Terraform, SQL and NoSQL, Jenkins, CI/CD, AWS (EC2, ECS, RDS, Aurora, Lambda, DynamoDB etc.), Serverless, Microservices, Distributed, fault-tolerant and scalable architecture, Large-scale refactoring</p>
<h5 id="102020---032021--platform-engineer">10/2020 - 03/2021 | Platform Engineer</h5>
<p><a href="https://www.airmap.com/">AirMap</a></p>
<ul>
<li>Designed, implemented, and maintained a nationally deployed registry for drone operators</li>
<li>Coordinated an international development team across the USA, Argentina, and Germany</li>
<li>Improved software development processes</li>
<li>Extended and improved the underlying AirMap platform</li>
<li>Conducted code reviews</li>
</ul>
<p>Keywords: Go, Python, Postgres, AWS, Kubernetes</p>
<h5 id="022020--102020--cto">02/2020 – 10/2020 | CTO</h5>
<p><a href="https://fino.ai">fino GmbH</a></p>
<ul>
<li>Managed and provided technical and disciplinary oversight to a team of more than twenty employees</li>
<li>Developed an API-first strategy encompassing software architecture, business processes, product strategy, and employee training</li>
<li>Analyzed and improved a microservice-based distributed architecture</li>
<li>Mentored and coached software developers, team leads, and product owners</li>
<li>Facilitated Scrum processes and conducted training for development teams</li>
<li>Coordinated recruitment and selection of nearshore and offshore freelancers and teams</li>
</ul>
<p>Keywords: Go, Java, Python, TypeScript, MongoDB, Elastic Stack, Sematext, Kubernetes, OTC (Open Telecom Cloud), CQRS</p>
<h5 id="072012--012020--software-architect">07/2012 – 01/2020 | Software Architect</h5>
<p><a href="https://www.micromata.de">Micromata GmbH</a></p>
<ul>
<li>Progressed from Software Engineer to Software Architect across 7+ years; led teams, coached engineers, served as Scrum Master</li>
<li><strong>Applications &amp; platforms:</strong> worldwide IAM system; KPI management platform (finance); fail-safe logistics application; route planning and dispatch system; package label sorting system; pharmaceutical sector application; SAP cloud platform applications; logistics performance troubleshooting</li>
<li><strong>Data &amp; analytics:</strong> enterprise analytics data lake; predictive maintenance platform (ML on industrial time series); Hadoop-based enterprise reporting platform; Big Data architecture (BMBF-funded research); personal data pseudonymization tool</li>
</ul>
<p>Keywords: Java, Spring Boot, Docker, AWS, Elasticsearch, Neo4j, PostgreSQL, Scala, Spark, Hadoop, React</p>
<h5 id="2015--2019--lecturer-for-web-engineering">2015 – 2019 | Lecturer for Web-Engineering</h5>
<p><a href="https://www.uni-kassel.de">University of Kassel</a></p>
<ul>
<li>Planned and conducted the master&rsquo;s lecture &ldquo;Web Engineering&rdquo;</li>
<li>Topics: Modern web architectures, frontend and backend development, deployment on cloud platforms, software tooling</li>
<li>Supervised project groups</li>
<li>Provided examination feedback and grading</li>
</ul>
<h5 id="042008--042012--fixed-term-research-associate">04/2008 – 04/2012 | Fixed-term Research Associate</h5>
<p><a href="https://www.uni-kassel.de">University of Kassel</a></p>
<ul>
<li>Planned, executed, and analyzed research projects in functional programming languages and parallel programming</li>
<li>Published research results</li>
<li>Presented at international conferences</li>
<li>Lectured on functional programming languages and foundational courses</li>
<li>Supervised student research assistants</li>
<li>Guided student project and thesis work</li>
</ul>
<h5 id="082007--122007--fixed-term-research-associate">08/2007 – 12/2007 | Fixed-term Research Associate</h5>
<p><a href="https://www.dcaiti.tu-berlin.de/">DCAITI / TU Berlin</a></p>
<ul>
<li>Planned and executed research projects</li>
<li>Investigated and developed multimodal human-machine software architectures</li>
<li>Conceptualized situation-based and adaptive learning mechanisms</li>
</ul>
<h4 id="education">Education</h4>
<h5 id="042008---042012--phd-in-computer-science">04/2008 - 04/2012 | Ph.D. in Computer Science</h5>
<p><a href="https://www.uni-kassel.de">University of Kassel</a></p>
<ul>
<li>Title: &ldquo;On the Benefits of Abstraction in Concurrent Haskell&rdquo;</li>
<li>Focused on advancing the field of functional programming and concurrency models.</li>
</ul>
<h5 id="102001---062007--diploma-in-computer-science-with-a-minor-in-mathematics">10/2001 - 06/2007 | Diploma in Computer Science with a minor in Mathematics</h5>
<p><a href="https://www.tu-clausthal.de/">Technical University of Clausthal</a></p>
<ul>
<li>Comparable to a Master&rsquo;s degree.</li>
<li>Comprehensive curriculum covering both theoretical and applied aspects of computer science and mathematics.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>Recommendations</title>
      <link>https://mlesniak.com/recommendations/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>mail@mlesniak.com (Dr. Michael Lesniak)</author>
      <guid>https://mlesniak.com/recommendations/</guid>
      <description>&lt;p class=&#34;availability&#34;&gt;Available from July 2026. &lt;a href=&#34;mailto:mail@mlesniak.com&#34;&gt;Get in touch.&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&#34;jonas-danker-cto&#34;&gt;Jonas Danker, CTO&lt;/h4&gt;
&lt;p&gt;&lt;span class=&#34;rec-date&#34;&gt;Micromerce GmbH — September 2024&lt;/span&gt;&lt;/p&gt;
&lt;div class=&#34;testimonials&#34;&gt;
&lt;blockquote&gt;
&lt;p&gt;&#34;Dr. Lesniak delivered excellent solutions for complex technical and business product requirements — goal-oriented, cost-optimised, performant, professional, and modern. The source code is very well structured and documented. His consulting, ideas, and concepts for architecture and platform development significantly advanced the platform. Communication regarding status, progress, and content was always transparent and professional.&lt;/p&gt;
&lt;p&gt;The collaboration was excellent. I would be happy to commission Dr. Lesniak again and recommend him without any reservations.&#34;&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p class="availability">Available from July 2026. <a href="mailto:mail@mlesniak.com">Get in touch.</a></p>
<h4 id="jonas-danker-cto">Jonas Danker, CTO</h4>
<p><span class="rec-date">Micromerce GmbH — September 2024</span></p>
<div class="testimonials">
<blockquote>
<p>"Dr. Lesniak delivered excellent solutions for complex technical and business product requirements — goal-oriented, cost-optimised, performant, professional, and modern. The source code is very well structured and documented. His consulting, ideas, and concepts for architecture and platform development significantly advanced the platform. Communication regarding status, progress, and content was always transparent and professional.</p>
<p>The collaboration was excellent. I would be happy to commission Dr. Lesniak again and recommend him without any reservations."</p>
</blockquote>
</div>
<hr>
<h4 id="vincent-sommer-head-of-engineering">Vincent Sommer, Head of Engineering</h4>
<p><span class="rec-date">milia.io — October 2025</span></p>
<div class="testimonials">
<blockquote>
<p>"Dr. Lesniak consistently delivered high-quality results. His solutions were characterised by technological modernity, excellent performance, and clear, well-structured code. Particularly valuable were his contributions to platform architecture development, integration of new technologies, and process automation.</p>
<p>His ability to quickly grasp complex requirements and translate them into robust solutions significantly advanced our team. We highly valued the collaboration and recommend him unconditionally as an external project partner."</p>
</blockquote>
</div>
<hr>
<h4 id="christian-dymek-founder">Christian Dymek, Founder</h4>
<p><span class="rec-date">MACH — April 2026</span></p>
<div class="testimonials">
<blockquote>
<p>"Michael brought exactly what we needed at the concept stage: sharp technical instincts, an honest outside perspective, and concrete recommendations we could act on immediately. In just a few sessions he helped us avoid costly architectural dead ends and gave our idea a solid technical foundation. A trusted sparring partner for any founder navigating early technical decisions."</p>
</blockquote>
</div>
<hr>
<p>Recommendations from my time as a former permanent employee, in roles ranging from Engineer to CTO (<a href="https://www.linkedin.com/in/drmichaellesniak/details/recommendations">more on LinkedIn</a>):</p>
<h4 id="andreas-schranzhofer-cto">Andreas Schranzhofer, CTO</h4>
<p><span class="rec-date">Scalable Capital — October 2024</span></p>
<div class="testimonials">
<blockquote>
<p>"Michael played a pivotal role at Scalable Capital, not only through his technical leadership but also by consistently being hands-on and deeply involved in every aspect of the work. He took full ownership of complex projects, driving them forward with a clear vision while creating an atmosphere where the team felt both challenged and supported.</p>
<p>What set Michael apart was his ability to lead while staying deeply embedded in the technical details. He was never afraid to roll up his sleeves, whether it was troubleshooting a critical issue or diving into the intricacies of system architecture. His hands-on approach meant he wasn't just overseeing the work — he was actively contributing to the most important technical decisions, ensuring everything was built to a high standard.</p>
<p>At the same time, Michael created an environment where people felt empowered to contribute. Whether it was organizing cross-team workshops or mentoring junior engineers, he had a talent for bringing people together and elevating the team's overall performance. His leadership style was collaborative, blending technical precision with a clear understanding of business goals.</p>
<p>Michael's ability to remain calm and focused under pressure was invaluable. Even during tight deadlines, his leadership and attention to detail ensured we consistently delivered. His impact went beyond the technical realm — he left a lasting positive impression on the team and our work culture.</p>
<p>If you're looking for someone who can not only architect and deliver complex technical solutions with precision but also inspire teams to excel, I can't recommend Michael highly enough."</p>
</blockquote>
</div>
<hr>
<h4 id="johannes-weiss-vp-engineering">Johannes Weiss, VP Engineering</h4>
<p><span class="rec-date">Scalable Capital — October 2024</span></p>
<div class="testimonials">
<blockquote>
<p>"Working with Michael during his time at Scalable Capital, I was continuously impressed by his exceptional technical acumen and leadership skills. As he moved from senior engineer to staff engineer and finally to principal engineer, Michael demonstrated an outstanding ability to address complex technical challenges in both programming and architectural design with depth and precision.</p>
<p>His structured approach to problem solving, combined with a long-term vision, enabled him to drive key technical initiatives from start to finish. In addition to addressing immediate technical issues, Michael ensured his solutions were future-proof, scalable, and aligned with the company's broader strategic goals.</p>
<p>What really set Michael apart was his ability to motivate and inspire the teams. His leadership fostered collaboration and growth, whether through mentoring young developers or leading the engineering organization in developing innovative solutions. His influence was felt across the board as he created a productive and positive work environment.</p>
<p>In summary, Michael is a highly skilled engineer and a natural leader who seamlessly combines technical excellence with a strategic understanding of business needs. I would highly recommend him to any organization looking for a leader who can balance both technical and business goals."</p>
</blockquote>
</div>
<hr>
<h4 id="aleksandar-jeremic-ceo">Aleksandar Jeremic, CEO</h4>
<p><span class="rec-date">fino digital — October 2024</span></p>
<div class="testimonials">
<blockquote>
<p>"During Michael's time as CTO at fino, his ability to bridge the gap between business goals and technical execution was truly exceptional. He had a unique talent for aligning our strategic objectives with a clear technical roadmap, ensuring that every engineering decision delivered real value for the company.</p>
<p>Michael's leadership was defined by a holistic approach to software engineering. He didn't just focus on code or systems; he took into account the entire business context — from product development to customer needs. This broader perspective allowed him to lead the engineering team in delivering scalable, reliable, and high-quality solutions.</p>
<p>In short, Michael excels at uniting business and technology. I highly recommend him to any company looking for a visionary yet practical leader who knows how to turn ideas into impactful, real-world solutions."</p>
</blockquote>
</div>
<hr>
<h4 id="dominik-goby-senior-application-architect">Dominik Goby, Senior Application Architect</h4>
<p><span class="rec-date">Amazon Web Services — October 2024</span></p>
<div class="testimonials">
<blockquote>
<p>"Michael's work at AirMap made a strong impression from the very beginning. His ability to handle constantly changing requirements was bar-raising throughout our time working together. No matter how tight the deadlines or how much the scope shifted, Michael remained focused and consistently delivered high-quality results.</p>
<p>What truly stood out about Michael was how well he worked within our international team. With colleagues spread across Europe, Argentina, and the U.S., clear communication and teamwork were essential. Michael excelled at both. He always fostered a positive atmosphere, making sure everyone was on the same page despite the distance and time zone differences.</p>
<p>In addition to his technical depth, Michael had a structured and thoughtful approach to tackling challenges. He was great at breaking down complex problems into manageable tasks, keeping the bigger picture in mind while ensuring we hit our deadlines. His level of ownership combined with his ability to stay organized and level-headed, even in high-pressure situations, was a huge asset to the team.</p>
<p>All in all, Michael was a key player in our team. His technical skills, adaptability, and collaborative nature made him an invaluable part of the group, and I wouldn't hesitate to recommend him!"</p>
</blockquote>
</div>
<hr>
<h4 id="gabriel-pavlovic-product-lead">Gabriel Pavlovic, Product Lead</h4>
<p><span class="rec-date">Scalable Capital — October 2024</span></p>
<div class="testimonials">
<blockquote>
<p>"Working with Michael at Scalable Capital was a great experience. He always took a holistic approach to problem-solving, thinking through the entire process from design to deployment, making sure everything lined up with the bigger product vision. Michael had a real knack for asking smart questions that helped shape the product and spot potential issues before we even started implementing.</p>
<p>On top of his technical skills, Michael was incredibly reliable. He always delivered high-quality work on time and brought a lot of positive energy to the team. His upbeat attitude and sense of humor made even the toughest projects more enjoyable. Whether we were dealing with tricky technical challenges or tight deadlines, I always knew I could count on Michael to deliver and keep the team's spirits up.</p>
<p>In short, Michael combines great technical expertise with a fun and collaborative work style. I highly recommend him to any team looking for a talented, dependable, and enjoyable engineer to work with."</p>
</blockquote>
</div>
<hr>
<h4 id="shady-botros-senior-software-engineer">Shady Botros, Senior Software Engineer</h4>
<p><span class="rec-date">Wolt (DoorDash) — October 2024</span></p>
<div class="testimonials">
<blockquote>
<p>"I've worked with Michael on a few projects. He's the kind of engineer who combines high-quality coding skills with a solid understanding of business goals. His knowledge of Java, Spring Boot, and AWS was super helpful, and thanks to his proactive attitude, we always hit our deadlines. He didn't need much direction; he just took charge and got things done.</p>
<p>If you're looking for someone who can bridge the gap between tech and business and actually deliver, I highly recommend Michael. He's great to work with and brings real value to the table."</p>
</blockquote>
</div>
<hr>
<h4 id="alexander-nikolai-gomes-devopsmlops-engineer">Alexander-Nikolai Gomes, DevOps/MLOps Engineer</h4>
<p><span class="rec-date">TECLION — October 2024</span></p>
<div class="testimonials">
<blockquote>
<p>"I had the pleasure of working with Michael for many years across a variety of projects and domains, and he consistently proved to be one of the most reliable engineers I've ever worked with. No matter the complexity or deadline pressure, Michael's dedication and work ethic were unwavering.</p>
<p>Over the years, I saw firsthand how his architectural skills helped shape scalable, robust solutions that were always built with future growth in mind. His strategic mindset ensured we were never just solving immediate problems but also preparing for what was ahead.</p>
<p>What truly sets Michael apart is his steady, proactive approach. He tackles challenges head-on, keeps everything on track, and always delivers on his commitments. For any team in need of a dependable engineer with deep architectural expertise, Michael is a top-notch choice."</p>
</blockquote>
</div>
]]></content:encoded>
    </item>
  </channel>
</rss>
