<?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/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>Sven Ruppert</title><link>https://svenruppert.com/</link><description>Sven Ruppert — Java Veteran, Speaker, Trainer &amp; Bushcrafter. Articles, talks, workshops and videos on Core Java, Cybersecurity, Vaadin and Developer Relations.</description><generator>Hugo</generator><language>en</language><managingEditor>sven.ruppert@gmail.com (Sven Ruppert)</managingEditor><webMaster>sven.ruppert@gmail.com (Sven Ruppert)</webMaster><copyright>© 2026 Sven Ruppert</copyright><atom:link href="https://svenruppert.com/index.xml" rel="self" type="application/rss+xml"/><image><url>https://svenruppert.com/img/sven-ruppert.jpg</url><title>Sven Ruppert</title><link>https://svenruppert.com/</link></image><lastBuildDate>Wed, 18 Mar 2026 07:05:00 +0000</lastBuildDate><item><title>A Vaadin Starter Project with a Clear Focus</title><link>https://svenruppert.com/posts/a-vaadin-starter-project-with-a-clear-focus/</link><pubDate>Wed, 18 Mar 2026 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/a-vaadin-starter-project-with-a-clear-focus/</guid><description>Many example projects overload the starting point by covering too many topics at once. Routing, data access, security, forms, theme customisations, and other integrations will then be presented in a single demo. This makes it more difficult for readers to recognise the project&rsquo;s actual structure.</description><content:encoded>&lt;![CDATA[<p>Many example projects overload the starting point by covering too many topics at once. Routing, data access, security, forms, theme customisations, and other integrations will then be presented in a single demo. This makes it more difficult for readers to recognise the project&rsquo;s actual structure.</p><p>This project takes a different approach. The focus is on a compact framework comprising AppShell, MainLayout, several views, a small service, its own theme, and prepared internationalisation. The aim is to make the basic building blocks of a Vaadin Flow application visible in a way that their interactions are comprehensible.</p><figure><img src="/images/2026/03/image.png" alt="Screenshot of a web application titled ‘Vaadin Flow Demo’ featuring a sidebar menu with options such as ‘Dashboard’, ‘Youtube’, and ‘About’. The main content area displays a header labeled ‘Headline’, a text input field for ‘Dein Name’, and a button labeled ‘Sag Hallo’." loading="lazy" decoding="async"/><p><strong>The source code for this project can be found at</strong></p><p><strong>GitHub at:<a href="https://3g3.eu/vdn-tpl">https://3g3.eu/vdn-tpl</a></strong></p><p>This is especially useful in Vaadin Flow, because the interface is created on the server side in Java. Classes such as AppShell, MainLayout, and MainView, therefore, shape the application&rsquo;s form early on. The article follows a Vaadin-centric perspective and does not primarily examine a specialist domain, but the structure of a usable flow project.</p><h2 id="technology-stack-and-project-idea">Technology stack and project idea</h2><p>The project relies on Vaadin Flow 25, Maven, Jetty, JDK 25, classic WAR packaging, its own theme and prepared internationalisation. This combination defines the application&rsquo;s technical framework.</p><p>Vaadin Flow bundles components, layouts, routing, and interactions within a single Java structure. Maven organises build, dependencies, and plugins. Jetty provides a lightweight servlet container for local operation and near-deployment execution. JDK 25 positions the project on a current Java basis, while the WAR packaging underlines its character as a classic web application.</p><p>Theme and internationalisation add two cross-cutting aspects to the stack: visual design and language-dependent content. As a result, the technical substructure is not only executable, but already prepared for typical extensions.</p><h2 id="project-structure-at-a-glance">Project Structure at a Glance</h2><p>The application follows the classic Maven structure with src/main/java for source code and src/main/resources for accompanying resources. This structure creates a familiar framework and separates executable code from configuration and language resources.</p><p>Within the Java part, responsibilities are structured to match the application structure. Entry point and global configurations form the outer frame. Layout classes define the overarching UI framework, views represent concrete pages, and service classes handle supporting logic.</p><p>Resources for internationalisation and the theme complement this. Translation files are located in the resource area, and visual adjustments are bundled in the expected places. This makes it easy to see in the repository where new pages, additional logic or design adjustments are classified.</p><h2 id="application-entry-point-appshell">Application Entry Point: AppShell</h2><p>The AppShell defines the application&rsquo;s global layer. This is where settings are anchored that affect not only a single page, but the browser appearance as a whole. These include theme activation, meta information, viewport information or other application-wide configurations.</p><p>In the project, this role is implemented in a compact class:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * Typical use cases of AppShell</span></span></span><span class="line"><span class="cl"><span class="cm"> * ✅ Viewport &amp; mobile optimization</span></span></span><span class="line"><span class="cl"><span class="cm"> * ✅ Setting metadata (SEO, security)</span></span></span><span class="line"><span class="cl"><span class="cm"> * ✅ Favicons, touch icons</span></span></span><span class="line"><span class="cl"><span class="cm"> * ✅ Global JavaScript snippets (analytics, monitoring)</span></span></span><span class="line"><span class="cl"><span class="cm"> * ✅ Global CSS (e.g., corporate branding)</span></span></span><span class="line"><span class="cl"><span class="cm"> * ✅ Selecting a theme for the entire app</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Meta</span><span class="p">(</span><span class="n">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"author"</span><span class="p">,</span><span class="w"/><span class="n">content</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Sven Ruppert"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Viewport</span><span class="p">(</span><span class="s">"width=device-width, initial-scale=1.0"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@PWA</span><span class="p">(</span><span class="n">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Project Base for Vaadin"</span><span class="p">,</span><span class="w"/><span class="n">shortName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Project Base"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Theme</span><span class="p">(</span><span class="s">"my-theme"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Push</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">AppShell</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">AppShellConfigurator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">configurePage</span><span class="p">(</span><span class="n">AppShellSettings</span><span class="w"/><span class="n">settings</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// settings.addFavIcon("icon",</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// "icons/my-favicon.png",</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// "32x32");</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">//</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// // Externes CSS</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// settings.addLink("stylesheet",</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// "https://cdn.example.com/styles/global.css");</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">//</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// // Externes Script</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// settings.addInlineWithContents(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// "console.log('Hello from AppShell!');",</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Inline.Wrapping.AUTOMATIC);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The annotations already show very well what kind of responsibility belongs in this class. @Meta complements global metadata, @Viewport defines behavior on mobile devices, @PWA describes basic progressive web app properties, @Theme(&ldquo;my-theme&rdquo;) activates the application-wide theme, and @Push unlocks server-side push communication. In this way, the AppShell bundles only the settings relevant to the browser&rsquo;s overall appearance.</p><p>In addition, the class implements AppShellConfigurator and provides a dedicated extensibility point with configurePage(AppShellSettings settings). Even though the method does not include any active settings in its current state, the commented-out examples already show typical fields of application: Favicons, global stylesheets, or inline scripts can be registered centrally here. This means the project has already been structurally prepared to accommodate such global adaptations.</p><p>Your job isn&rsquo;t to render business content or bundle component logic. Rather, it defines the frame in which the actual surface will later move. This creates a clearly recognisable place for global decisions, rather than scattering them across views or layout classes.</p><h2 id="the-basic-structure-of-the-ui-with-mainlayout">The Basic Structure of the UI with MainLayout</h2><p>The MainLayout forms the permanent structural level of the surface. It bundles navigation, the header area, and the embedding of the content area, and thus determines how individual pages are presented within the same application.</p><p>In the project, this task is implemented in its own layout class, which inherits directly from AppLayout:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainLayout</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">AppLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">MainLayout</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">createHeader</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">createHeader</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">H1</span><span class="w"/><span class="n">appTitle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">H1</span><span class="p">(</span><span class="s">"Vaadin Flow Demo"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SideNav</span><span class="w"/><span class="n">views</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">getPrimaryNavigation</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Scroller</span><span class="w"/><span class="n">scroller</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Scroller</span><span class="p">(</span><span class="n">views</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">scroller</span><span class="p">.</span><span class="na">setClassName</span><span class="p">(</span><span class="n">LumoUtility</span><span class="p">.</span><span class="na">Padding</span><span class="p">.</span><span class="na">SMALL</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">DrawerToggle</span><span class="w"/><span class="n">toggle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DrawerToggle</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">H2</span><span class="w"/><span class="n">viewTitle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">H2</span><span class="p">(</span><span class="s">"Headline"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">HorizontalLayout</span><span class="w"/><span class="n">wrapper</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">toggle</span><span class="p">,</span><span class="w"/><span class="n">viewTitle</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">wrapper</span><span class="p">.</span><span class="na">setAlignItems</span><span class="p">(</span><span class="n">FlexComponent</span><span class="p">.</span><span class="na">Alignment</span><span class="p">.</span><span class="na">CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">wrapper</span><span class="p">.</span><span class="na">setSpacing</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="n">viewHeader</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">VerticalLayout</span><span class="p">(</span><span class="n">wrapper</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">viewHeader</span><span class="p">.</span><span class="na">setPadding</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">viewHeader</span><span class="p">.</span><span class="na">setSpacing</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addToDrawer</span><span class="p">(</span><span class="n">appTitle</span><span class="p">,</span><span class="w"/><span class="n">scroller</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addToNavbar</span><span class="p">(</span><span class="n">viewHeader</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setPrimarySection</span><span class="p">(</span><span class="n">Section</span><span class="p">.</span><span class="na">DRAWER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">SideNav</span><span class="w"/><span class="nf">getPrimaryNavigation</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SideNav</span><span class="w"/><span class="n">sideNav</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SideNav</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">sideNav</span><span class="p">.</span><span class="na">addItem</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s">"Dashboard"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">MainView</span><span class="p">.</span><span class="na">PATH</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">DASHBOARD</span><span class="p">.</span><span class="na">create</span><span class="p">()),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s">"Youtube"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">YoutubeView</span><span class="p">.</span><span class="na">PATH</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">CART</span><span class="p">.</span><span class="na">create</span><span class="p">()),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s">"About"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">AboutView</span><span class="p">.</span><span class="na">PATH</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">USER_HEART</span><span class="p">.</span><span class="na">create</span><span class="p">())</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">sideNav</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Recurring elements are centrally housed here rather than rebuilt in each view. On this basis, individual pages can concentrate on their own content, while the MainLayout provides the overarching framework. Vaadin&rsquo;s AppLayout provides a suitable basis for this.</p><p>Between AppShell and views, the MainLayout thus serves as the connecting UI layer: global settings remain outside, concrete content is within the views, and in between lies the permanent interface structure.</p><h2 id="setting-up-navigation-and-routes-cleanly">Setting up navigation and routes cleanly</h2><p>The routing structure determines the paths to the individual views. In Vaadin Flow, this is done directly at the respective class via @Route. This keeps routing closely related to the visible page structure.</p><p>In the project, this principle is evident in the view classes. The start page uses an empty path and binds to the MainLayout at the same time:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * The main view contains a text field for getting the username and a button</span></span></span><span class="line"><span class="cl"><span class="cm"> * that shows a greeting message in a notification.</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainView</span><span class="p">.</span><span class="na">PATH</span><span class="p">,</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainLayout</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">LocaleChangeObserver</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">YOUR_NAME</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"your.name"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">SAY_HELLO</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"say.hello"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">PATH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">""</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">GreetService</span><span class="w"/><span class="n">greetService</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GreetService</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">button</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="n">textField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">MainView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">button</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="n">greetService</span><span class="p">.</span><span class="na">greet</span><span class="p">(</span><span class="n">textField</span><span class="p">.</span><span class="na">getValue</span><span class="p">())));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">textField</span><span class="p">,</span><span class="w"/><span class="n">button</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">localeChange</span><span class="p">(</span><span class="n">LocaleChangeEvent</span><span class="w"/><span class="n">localeChangeEvent</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">button</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="n">getTranslation</span><span class="p">(</span><span class="n">SAY_HELLO</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">textField</span><span class="p">.</span><span class="na">setLabel</span><span class="p">(</span><span class="n">getTranslation</span><span class="p">(</span><span class="n">YOUR_NAME</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The other pages also follow the same pattern. AboutView and YoutubeView each define their paths via their own constants and are also embedded in the MainLayout:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">AboutView</span><span class="p">.</span><span class="na">PATH</span><span class="p">,</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainLayout</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">AboutView</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">PATH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"about"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">AboutView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">H1</span><span class="w"/><span class="n">title</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">H1</span><span class="p">(</span><span class="s">"About"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">H2</span><span class="w"/><span class="n">subtitle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">H2</span><span class="p">(</span><span class="s">"Vaadin Flow Demo Application"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Paragraph</span><span class="w"/><span class="n">description</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="s">"This is a demo application built with Vaadin Flow "</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"framework to showcase various UI components and features."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Paragraph</span><span class="w"/><span class="n">version</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="s">"Version: 1.0.0"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Paragraph</span><span class="w"/><span class="n">author</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="s">"Created by: Sven Ruppert"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Paragraph</span><span class="w"/><span class="n">bio</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="s">"""</span></span></span><span class="line"><span class="cl"><span class="s"> Sven Ruppert has been involved in software development for more than 20 years. \</span></span></span><span class="line"><span class="cl"><span class="s"> As developer advocate he is constantly looking for innovations in software development. \</span></span></span><span class="line"><span class="cl"><span class="s"> He is speaking internationally at conferences and has authored numerous technical articles and books."""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Paragraph</span><span class="w"/><span class="n">homepage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="s">"Visit my website: "</span><span class="p">),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Anchor</span><span class="p">(</span><span class="s">"https://www.svenruppert.com"</span><span class="p">,</span><span class="w"/><span class="s">"www.svenruppert.com"</span><span class="p">,</span><span class="w"/><span class="n">BLANK</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Image</span><span class="w"/><span class="n">vaadinLogo</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Image</span><span class="p">(</span><span class="s">"images/vaadin-logo.png"</span><span class="p">,</span><span class="w"/><span class="s">"Vaadin Logo"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">vaadinLogo</span><span class="p">.</span><span class="na">setWidth</span><span class="p">(</span><span class="s">"200px"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setSpacing</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setPadding</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setAlignItems</span><span class="p">(</span><span class="n">Alignment</span><span class="p">.</span><span class="na">CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">title</span><span class="p">,</span><span class="w"/><span class="n">subtitle</span><span class="p">,</span><span class="w"/><span class="n">description</span><span class="p">,</span><span class="w"/><span class="n">version</span><span class="p">,</span><span class="w"/><span class="n">author</span><span class="p">,</span><span class="w"/><span class="n">bio</span><span class="p">,</span><span class="w"/><span class="n">homepage</span><span class="p">,</span><span class="w"/><span class="n">vaadinLogo</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>These excerpts show several important points. First, routing is defined directly where the respective page is implemented. Secondly, the layout = MainLayout.class assignment makes it clear on which surface frame the view will appear later. Third, the paths are not distributed as magic strings in many places, but bundled via PATH constants at the classes themselves.</p><p>Routes do not only take on a technical function. They organise the application into accessible areas and make it visible how pages are named and separated from each other. In conjunction with the MainLayout, this creates the navigation structure that users use to switch between individual views.</p><p>It is important to distinguish between routing and navigation: Routing defines accessibility, navigation defines its representation in the interface.</p><h2 id="the-first-view-mainview-as-a-minimal-interaction-example">The First View: MainView as a Minimal Interaction Example</h2><p>With MainView, the application can be experienced as a concrete page on first launch. It shows a simple interaction pattern of input, action, and visible reaction, making the basic idea of server-side UI programming in Vaadin immediately tangible.</p><p>In the project, this first view is deliberately kept compact:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * The main view contains a text field for getting the username and a button</span></span></span><span class="line"><span class="cl"><span class="cm"> * that shows a greeting message in a notification.</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainView</span><span class="p">.</span><span class="na">PATH</span><span class="p">,</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainLayout</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">LocaleChangeObserver</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">YOUR_NAME</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"your.name"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">SAY_HELLO</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"say.hello"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">PATH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">""</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">GreetService</span><span class="w"/><span class="n">greetService</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GreetService</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">button</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="n">textField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">MainView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">button</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="n">greetService</span><span class="p">.</span><span class="na">greet</span><span class="p">(</span><span class="n">textField</span><span class="p">.</span><span class="na">getValue</span><span class="p">())));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">textField</span><span class="p">,</span><span class="w"/><span class="n">button</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">localeChange</span><span class="p">(</span><span class="n">LocaleChangeEvent</span><span class="w"/><span class="n">localeChangeEvent</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">button</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="n">getTranslation</span><span class="p">(</span><span class="n">SAY_HELLO</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">textField</span><span class="p">.</span><span class="na">setLabel</span><span class="p">(</span><span class="n">getTranslation</span><span class="p">(</span><span class="n">YOUR_NAME</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This class shows the basic pattern of a Vaadin view very well. The page is directly linked to the routing via @Route and is also embedded in the MainLayout. TextField and Button define two central UI components that are directly assembled in the constructor. Clicking on the button triggers a server-side action and adds a new paragraph with the result to the layout. Interface, event handling and reaction thus remain merged in a closed Java model.</p><p>The already integrated multilingualism is also remarkable. Through LocaleChangeObserver, the view reacts to a language change and updates identifiers such as button text and field labels directly using translation keys. In this way, the class combines interaction and internationalisation in a very small but complete example.</p><p>The actual text logic is no longer directly in the view, but in a small service class:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">GreetService</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">greet</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">name</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">name</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Hello anonymous user"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Hello "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>It is precisely this reduction that makes it easy to see how a first interactive page in Vaadin is structured. Components, event handling, layout integration, and a small amount of outsourced logic are already intertwined without the need for additional infrastructure. At the same time, the MainView remains part of the larger application framework of Routing and MainLayout.</p><h2 id="separating-ui-logic-and-business-logic">Separating UI Logic and Business Logic</h2><p>With the first interactive view, the question of responsibility arises. A view describes the interface, inputs, and reactions to user actions. Supporting logic, on the other hand, can be outsourced to its own classes.</p><p>In the project, this role is taken over by the GreetService. Even though its task remains small, it shows the boundary between component control in the view and application-oriented logic outside the interface. This separation facilitates later expansions, because additional rules or preparation steps do not have to be accommodated directly in the view.</p><h2 id="thinking-about-internationalisation-from-the-outset">Thinking about internationalisation from the outset</h2><p>Texts are created directly in a Vaadin application, where components are built. This is precisely why it makes sense to remove language-dependent content from views early and manage it through translation keys and resource files.</p><p>In the project, this is visible directly in the MainView. The class implements LocaleChangeObserver and obtains labels not as hardwired strings, but via translation keys:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * The main view contains a text field for getting the username and a button</span></span></span><span class="line"><span class="cl"><span class="cm"> * that shows a greeting message in a notification.</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainView</span><span class="p">.</span><span class="na">PATH</span><span class="p">,</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainLayout</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">LocaleChangeObserver</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">YOUR_NAME</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"your.name"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">SAY_HELLO</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"say.hello"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">PATH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">""</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">GreetService</span><span class="w"/><span class="n">greetService</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GreetService</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">button</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="n">textField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">MainView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">button</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="n">greetService</span><span class="p">.</span><span class="na">greet</span><span class="p">(</span><span class="n">textField</span><span class="p">.</span><span class="na">getValue</span><span class="p">())));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">textField</span><span class="p">,</span><span class="w"/><span class="n">button</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">localeChange</span><span class="p">(</span><span class="n">LocaleChangeEvent</span><span class="w"/><span class="n">localeChangeEvent</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">button</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="n">getTranslation</span><span class="p">(</span><span class="n">SAY_HELLO</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">textField</span><span class="p">.</span><span class="na">setLabel</span><span class="p">(</span><span class="n">getTranslation</span><span class="p">(</span><span class="n">YOUR_NAME</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The localeChange(&hellip;) method, in particular, demonstrates the practical implementation of multilingualism. Instead of entering text directly when components are created, they are resolved via getTranslation(&hellip;) by key. This allows the same view to display different labels depending on the active language.</p><p>The associated resources are located in the project at src/main/resources/vaadin-i18n. The default language is defined in translations.properties:</p><p>**your.name=Your name</p><p>**say.hello=Say Hello</p><p>There is a separate file for German translations_de.properties:</p><p>**your.name=Your name</p><p>**say.hello=Say hello</p><p>These files clearly show how the language-dependent content is extracted from the Java code. The view only knows the keys your.name and say.hello, while the actual texts are maintained in the resources. This means that the interface remains linguistically adaptable without having to rephrase component classes themselves.</p><p>Multilingualism is already prepared in the project. Texts are not treated as fixed components of individual components, but as content that is provided depending on the active language. In conjunction with language-dependent interface updates, it becomes apparent that internationalisation not only comprises files but also describes the application&rsquo;s behaviour.</p><h2 id="additional-views-as-a-blueprint-for-your-own-pages">Additional views as a blueprint for your own pages</h2><p>In addition to the MainView, AboutView, and YouTubeView, the application also displays other page types. They stand for more information-oriented content and make it clear that the same structure works not only for interactive input pages, but also for simpler content pages.</p><p>This makes it possible to see how new views can be embedded in routing, navigation and MainLayout without introducing their own special structures. Different page types can thus be implemented within a common framework.</p><h2 id="build-start-and-development-mode">Build, Start, and Development Mode</h2><p>Hands-on work with the project includes build, local startup, and development mode. Maven bundles dependencies, build steps and plugin configurations for this purpose. This is particularly relevant for Vaadin because, in addition to the Java code, themes, resources, and Vaadin-specific processing are integrated into the development process.</p><p>Jetty takes over the local operation via a lightweight servlet container. This keeps the path from the source code to the running application short. Changes to views, layouts, translations or theme rules can be checked directly in development mode.</p><p>WAR packaging fits into this model and keeps the application in a classic web application context.</p><h2 id="conclusion">Conclusion</h2><p>The project makes the central layers of a Vaadin Flow application visible in a compact form: global configuration via the AppShell, permanent UI structure in the MainLayout, routing for accessible pages, concrete views for content, an outsourced service for supporting logic, prepared internationalisation, a separate theme and a clear build and start path.</p><p>Especially in this compact form, it is easy to understand how these building blocks work together. The repository thus shows not only individual Vaadin techniques, but also the structure of an application whose structure remains consistently recognisable from the first entry point to the development mode.</p>
]]></content:encoded><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2026/03/ChatGPT-Image-9.-Marz-2026-15_00_40.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2026/03/ChatGPT-Image-9.-Marz-2026-15_00_40.jpeg"/><enclosure url="https://svenruppert.com/images/2026/03/ChatGPT-Image-9.-Marz-2026-15_00_40.jpeg" type="image/jpeg" length="0"/></item><item><title>Practical i18n in Vaadin: Resource Bundles, Locale Handling and UI Language Switching</title><link>https://svenruppert.com/posts/practical-i18n-in-vaadin-resource-bundles-locale-handling-and-ui-language-switching/</link><pubDate>Wed, 11 Mar 2026 07:09:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/practical-i18n-in-vaadin-resource-bundles-locale-handling-and-ui-language-switching/</guid><description>Modern web applications are rarely used only by users with the same language. Even internal tools often reach international teams or are used in different countries. A multilingual user interface is therefore not a luxury feature, but an important part of the user experience.</description><content:encoded>&lt;![CDATA[<p>Modern web applications are rarely used only by users with the same language. Even internal tools often reach international teams or are used in different countries. A multilingual user interface is therefore not a luxury feature, but an important part of the user experience.</p><p>The open-source project URL-Shortener also benefits from a clear internationalisation strategy. The application is intended for developers, administrators, and other users who want to manage, analyse, or distribute links. To ensure that the interface remains understandable regardless of the user&rsquo;s origin, the application supports multiple languages.</p><figure><img src="/images/2026/03/grafik-1024x503.png" alt="Screenshot of a URL shortener interface displaying a list of shortened URLs, their codes, original links, creation dates, and status indicators." loading="lazy" decoding="async"/><p><strong>The project can be found on GitHub at the URL:</strong><a href="https://3g3.eu/url">https://3g3.eu/url</a></p><p>This article shows how the URL shortener UI has been enhanced with simple, robust internationalisation. The implementation is based entirely on Vaadin Flow&rsquo;s capabilities and deliberately uses only a few additional helper classes to keep the architecture clear.</p><ol><li><a href="https://svenruppert.com/2026/03/04/practical-i18n-in-vaadin-resource-bundles-locale-handling-and-ui-language-switching/#internationalisation-in-vaadin">Internationalisation in Vaadin</a></li><li><a href="https://svenruppert.com/2026/03/04/practical-i18n-in-vaadin-resource-bundles-locale-handling-and-ui-language-switching/#structuring-translation-resources">Structuring Translation Resources</a></li><li><a href="https://svenruppert.com/2026/03/04/practical-i18n-in-vaadin-resource-bundles-locale-handling-and-ui-language-switching/#simplified-access-to-translations-with-i18nsupport">Simplified access to translations with I18nSupport</a></li><li><a href="https://svenruppert.com/2026/03/04/practical-i18n-in-vaadin-resource-bundles-locale-handling-and-ui-language-switching/#determining-the-user-locale">Determining the user locale</a></li><li><a href="https://svenruppert.com/2026/03/04/practical-i18n-in-vaadin-resource-bundles-locale-handling-and-ui-language-switching/#session-based-voice-management">Session-based voice management</a></li><li><a href="https://svenruppert.com/2026/03/04/practical-i18n-in-vaadin-resource-bundles-locale-handling-and-ui-language-switching/#language-switching-in-the-user-interface">Language switching in the user interface</a></li><li><a href="https://svenruppert.com/2026/03/04/practical-i18n-in-vaadin-resource-bundles-locale-handling-and-ui-language-switching/#conclusion-and-outlook">Conclusion and outlook</a></li></ol><p>The solution has several key objectives. On the one hand, the users&rsquo; language is to be automatically recognised based on the locale transmitted by the browser. In addition, it must be possible to manage multiple translations via resource bundles without creating additional administrative overhead in the application code. At the same time, the user should be able to change the language at any time during a running session. Another important goal is to keep the implementation lean and deliberate and to dispense with additional frameworks so that internationalisation can be implemented directly within the existing application without complex infrastructure.</p><p>Currently, the application supports three languages:</p><ul><li>English</li><li>German</li><li>Finnish</li></ul><p>The choice of these languages is deliberately pragmatic. English serves as the standard language, while German and Finnish depict real-life usage scenarios from the project&rsquo;s environment.</p><p>The focus of this article is not only on the technical implementation but also on how internationalisation can be integrated into a Java web application in a way that keeps the source code clearly structured and maintainable. To do this, we examine both the organisation of translation resources and the handling of locales within the application.</p><p>In the following course, it is explained step by step how Vaadin recognises and loads translation resources, how translation keys can be structured sensibly, and how the user&rsquo;s language is determined based on the browser locale. In addition, it shows how simple language switching can be integrated directly into the user interface, allowing users to switch the desired language during a running session.</p><p>This creates an internationalisation solution that is deliberately kept simple but can be easily expanded if other languages are added later.</p><h2 id="internationalisation-in-vaadin">Internationalisation in Vaadin</h2><p>Vaadin Flow already provides basic support for internationalisation. Applications can provide translations via so-called resource bundles, which are automatically recognised and loaded by the runtime environment. In this way, user interfaces can be operated in several languages without much additional effort.</p><p>The central mechanism relies on translation files stored in a special directory within the project. By default, Vaadin searches for translation resources in the vaadin-i18n folder within the classpath. All files stored there are automatically recognised and are available to the application at runtime.</p><p>The translation files follow the familiar structure of Java Resource Bundles. A base file contains the standard translations, while additional files provide language-specific variants. The language is defined by a language suffix in the file name.</p><p>A typical setup looks like this, for example:</p><p><strong>vaadin-i18n/</strong></p><p>** translations.properties**</p><p>** translations_de.properties**</p><p>** translations_fi.properties**</p><p>The file without a language suffix serves as the default resource. In many projects, it contains the English texts. As soon as Vaadin determines an active locale, the framework automatically attempts to load the appropriate translation file. If a language-specific variant exists, it is used. Otherwise, Vaadin falls back on the default resource.</p><p>This behaviour corresponds to the classic fallback mechanism of Java Resource Bundles. This means that the application remains functional even if individual translations are not yet complete.</p><p>For the user interface, this means that text is no longer defined directly in the source code but is referenced via unique translation keys. These keys are then resolved at runtime via the active locale.</p><p>Vaadin provides the getTranslation() method for this purpose, which can be used directly in UI components. This method is used to pass a translation key to the framework, after which Vaadin determines the appropriate text from the loaded resources.</p><p>Thus, the resource bundle system forms the basis for all other internationalisation mechanisms within the application. On this basis, additional concepts such as language switching, session-based locale management or structured translation keys can then be developed.</p><h2 id="structuring-translation-resources">Structuring Translation Resources</h2><p>After looking at Vaadin&rsquo;s basic internationalisation support in the previous chapter, an important question quickly arises in practice: How should translation resources be organised within a project to remain maintainable in the long term?</p><p>In the URL shortener project, all translations are stored in so-called resource bundles. These are located in the directory src/main/resources/vaadin-i18n/. Vaadin automatically detects this folder and loads the translation files it contains at runtime.</p><p>The structure of the files follows the conventions of Java Resource Bundles. A base file contains the standard translations, while additional files provide language-specific variants.</p><p><strong>src/main/resources/vaadin-i18n/</strong></p><p>** translations.properties**</p><p>** translations_de.properties**</p><p>** translations_fi.properties**</p><p>The translations.properties file serves as the default resource. This project contains the English texts. For other languages, additional files with a language suffix are created. The language is defined by the ISO language code in the file name.</p><p>For example, the German version contains the file translations_de.properties, while the Finnish version contains translations_fi.properties.</p><p>Within these files, the actual translations are defined via key-value pairs. Each key corresponds to a specific text in the user interface.</p><p><strong>main.appTitle=URL Shortener</strong></p><p><strong>main.logout=Logout</strong></p><p><strong>nav.overview=Overview</strong></p><p><strong>nav.create=Create</strong></p><p><strong>nav.youtube=Youtube</strong></p><p><strong>nav.about=About</strong></p><p>The keys themselves follow a simple naming convention. They are often built according to the pattern bereich.element. This allows texts that belong together to be logically grouped.</p><p>This structure makes it easier to keep track of a large number of translations. At the same time, it prevents name conflicts between different areas of the application.</p><p>Another advantage of this structure is that translations remain completely separate from the application code. The source code only references the keys, while the actual texts are defined in the resource bundles.</p><p>This allows translations to be expanded or corrected later without changes to the application code.</p><p>This clear separation between code and translation resources provides an important basis for further internationalisation of the application. The next step will therefore look at how these translation keys can be used comfortably within the user interface.</p><h2 id="simplified-access-to-translations-with-i18nsupport">Simplified access to translations with I18nSupport</h2><p>Once the structure of the translation resources has been determined, another question arises in practical development: How can these translations be used as comfortably as possible within the user interface?</p><p>Vaadin basically provides the getTranslation() method, which can be used to resolve translation keys at runtime. This method is available, for example, within UI components and allows you to use a key to determine the appropriate text from the loaded resource bundles.</p><p>In practice, however, direct use of this method often results in recurring boilerplate code. Especially in larger user interfaces, translations have to be resolved in many different places. This quickly results in redundant calls and less readable code.</p><p>To make this access easier, the URL shortener project uses a small helper interface called I18nSupport. This interface encapsulates access to the translation function and provides a compact method for querying translations within the UI components.</p><p>A simplified example of this interface looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">I18nSupport</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">default</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">tr</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">fallback</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">getTranslation</span><span class="p">(</span><span class="n">key</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This auxiliary method allows translations to be used much more compactly within the application. Instead of working directly with the Vaadin API, a single method call suffices.</p><p>An example from the application&rsquo;s MainLayout illustrates this:</p><p><strong>H1 appTitle = new H1(tr(&ldquo;main.appTitle&rdquo;, &ldquo;URL Shortener&rdquo;));</strong></p><p>This keeps the source code clear and the actual text completely separate from the user interface implementation.</p><p>Another advantage of this solution is that access to translations can be adjusted centrally. If the implementation changes later – for example, due to additional fallback mechanisms or logging – this can be done within the helper interface without having to adapt all UI components.</p><p>I18nSupport thus forms a small but effective abstraction layer on top of the Vaadin i18n API. It ensures that translations can be used consistently throughout the application with as little boilerplate code as possible.</p><h2 id="determining-the-user-locale">Determining the user locale</h2><p>Once translation resources have been structured and convenient access has been provided via I18nSupport, the next step is to ask a central question: What language should the application use when it is first launched?</p><p>In web applications, this decision is usually made based on the browser&rsquo;s language preferences. Modern browsers send a so-called Accept-Language header with every HTTP request that contains a prioritised list of preferred languages. Based on this information, the application can decide which locale to use initially.</p><p>Vaadin Flow already takes this information into account during user interface initialisation. When building a new UI, the browser&rsquo;s locale is automatically detected and set as the current locale. This allows an application to select the appropriate language when the interface is first rendered.</p><p>In practice, however, it rarely makes sense to support every language reported by the browser fully. Many applications only offer a limited number of translations. Therefore, the browser-reported locale must first be compared with the application&rsquo;s actually supported languages.</p><p>In the URL shortener project, this matching is implemented via a small helper class called LocaleSelection. This class defines the application&rsquo;s supported languages and provides methods to map a requested locale to an available variant.</p><p>A simplified example looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Locale</span><span class="w"/><span class="nf">match</span><span class="p">(</span><span class="n">Locale</span><span class="w"/><span class="n">requested</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">requested</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">EN</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"/><span class="n">language</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">requested</span><span class="p">.</span><span class="na">getLanguage</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"/><span class="n">SUPPORTED</span><span class="p">.</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">l</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">l</span><span class="p">.</span><span class="na">getLanguage</span><span class="p">().</span><span class="na">equals</span><span class="p">(</span><span class="n">language</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">findFirst</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">orElse</span><span class="p">(</span><span class="n">EN</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The method first checks which language the browser requested. It then checks whether this language is among the application&rsquo;s supported languages. If this is the case, the corresponding locale is adopted. Otherwise, the application&rsquo;s default language is used.</p><p>This strategy ensures that the application remains consistent even if a browser reports a language for which there are no complete translations. At the same time, the implementation remains clear and easily expandable.</p><p>The next step is how to keep the chosen language stable during a session. Therefore, in the following chapter, we will look at how a user&rsquo;s locale is stored and managed within the Vaadin session.</p><h2 id="session-based-voice-management">Session-based voice management</h2><p>After describing in the previous chapter how the initial language of an application can be determined based on the browser locale, another important question arises in practice: How does this decision remain stable throughout the application&rsquo;s use?</p><p>In a typical web application, a user session consists of multiple HTTP requests. During this time, the user expects the interface to be consistently displayed in the same language. If the language were to be determined exclusively by the browser locale for each request, this could lead to unexpected changes – especially if a user manually switches languages within the application.</p><p>For this reason, the URL shortener project stores the selected language within the Vaadin session. The session represents a server-side context that is retained across multiple requests and is therefore well-suited to storing user-specific settings, such as the current locale.</p><p>Vaadin itself already offers basic support for this. Each VaadinSession has an associated locale that can be set and queried by the application. If this locale is changed, it directly affects the resolution of translation keys in the user interface.</p><p>In the project, the session locale is also managed using the LocaleSelection helper class. In addition to the previously shown method for selecting a supported language, this class also provides methods to store or read the locale in the session.</p><p>A simplified example for storing the locale looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setToSession</span><span class="p">(</span><span class="n">VaadinSession</span><span class="w"/><span class="n">session</span><span class="p">,</span><span class="w"/><span class="n">Locale</span><span class="w"/><span class="n">locale</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">session</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="n">SESSION_KEY</span><span class="p">,</span><span class="w"/><span class="n">locale</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>When changing the language, the selected locale is saved in the session and set directly on the current UI. This ensures that the new language becomes active immediately and is retained for all subsequent interactions within the same session.</p><p>This approach has several advantages. For one, a user&rsquo;s language remains consistent throughout the session. On the other hand, the language can be changed at any time by a user action without the need for additional persistence mechanisms such as cookies or database entries.</p><p>In addition, the implementation remains deliberately simple. The application uses only the existing Vaadin mechanisms and adds only a small helper class for central management of the supported locales.</p><p>The next chapter shows how this session-based locale is eventually linked to the user interface and how users can switch languages directly within the application.</p><h2 id="language-switching-in-the-user-interface">Language switching in the user interface</h2><p>After describing in the previous chapter how to store the selected language in the Vaadin session, the practical question now arises: how can users change this language in the first place? Internationalisation is only useful if users can actively switch the interface language.</p><p>In the URL shortener project, language switching has been integrated directly into the MainLayout. This layout forms the central structure of the user interface and includes, among other things, the navigation bar and various status and control elements. This makes it ideal for placing a compact language switch.</p><p>Instead of a classic drop-down menu, the application uses a small group of buttons, each representing a language. The buttons are displayed as flags, making the desired language recognisable at a glance.</p><p>The user interface shows three options:</p><ul><li>German</li><li>English</li><li>Finnish</li></ul><figure><img src="/images/2026/03/grafik-1.png" alt="User interface showing language options with German, English, and Finnish flags, the name ‘EclipseStore,’ and a logout button." loading="lazy" decoding="async"/><p>Each of these buttons changes the current locale when clicked. Technically, the first step is to check which language has been chosen. This language is then set in both the current UI and the Vaadin session.</p><p>The switch&rsquo;s specific design is deliberately kept small and does not require any additional components or dependencies. Instead of a ComboBox, a horizontal button group is rendered, which acts like a &ldquo;segmented control&rdquo;: three round buttons in a slightly separated bar. The bar itself is merely a horizontal layout, with styling handled via Lumo CSS variables, so the look blends harmoniously with the rest of the theme.</p><p>When the switch is created, the currently effective locale is first determined. It is crucial that not only the browser locale is considered, but that a value already stored in the session takes precedence. This is exactly what LocaleSelection.resolveAndStore(&hellip;) is used for. The result is the &ldquo;current&rdquo; locale, which is used to mark the active button.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Component</span><span class="w"/><span class="nf">createLanguageSwitch</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Locale</span><span class="w"/><span class="n">current</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">LocaleSelection</span><span class="p">.</span><span class="na">resolveAndStore</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">VaadinSession</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">getLocale</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Button</span><span class="w"/><span class="n">de</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">flagButton</span><span class="p">(</span><span class="n">LocaleSelection</span><span class="p">.</span><span class="na">DE</span><span class="p">,</span><span class="w"/><span class="n">current</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Button</span><span class="w"/><span class="n">en</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">flagButton</span><span class="p">(</span><span class="n">LocaleSelection</span><span class="p">.</span><span class="na">EN</span><span class="p">,</span><span class="w"/><span class="n">current</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Button</span><span class="w"/><span class="n">fi</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">flagButton</span><span class="p">(</span><span class="n">LocaleSelection</span><span class="p">.</span><span class="na">FI</span><span class="p">,</span><span class="w"/><span class="n">current</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">HorizontalLayout</span><span class="w"/><span class="n">bar</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">de</span><span class="p">,</span><span class="w"/><span class="n">en</span><span class="p">,</span><span class="w"/><span class="n">fi</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">bar</span><span class="p">.</span><span class="na">setSpacing</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">bar</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"gap"</span><span class="p">,</span><span class="w"/><span class="s">"6px"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"padding"</span><span class="p">,</span><span class="w"/><span class="s">"2px"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"border-radius"</span><span class="p">,</span><span class="w"/><span class="s">"999px"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"background"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-contrast-5pct)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"/><span class="n">bar</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The actual logic lies in creating a single button. Each button only gets a chip with an emoji flag as content. This seems trivial, but it&rsquo;s practical: it requires no assets, no additional files, and no build steps. Styling and interaction take place directly on the button.</p><p>Three points are important here: First, the buttons are shaped into a compact, round form so that they are perceived as a group that belongs together. Second, the active state is calculated based on the language (getLanguage()), because regional variants such as de-DE or de-AT should not affect the UI. Third, a click triggers the actual locale switch.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="nf">flagButton</span><span class="p">(</span><span class="n">Locale</span><span class="w"/><span class="n">locale</span><span class="p">,</span><span class="w"/><span class="n">Locale</span><span class="w"/><span class="n">current</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"/><span class="n">flag</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">flagEmoji</span><span class="p">(</span><span class="n">locale</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Button</span><span class="w"/><span class="n">b</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">flag</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">b</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_TERTIARY_INLINE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">b</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"width"</span><span class="p">,</span><span class="w"/><span class="s">"38px"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"height"</span><span class="p">,</span><span class="w"/><span class="s">"32px"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"min-width"</span><span class="p">,</span><span class="w"/><span class="s">"38px"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"border-radius"</span><span class="p">,</span><span class="w"/><span class="s">"999px"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"padding"</span><span class="p">,</span><span class="w"/><span class="s">"0"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"line-height"</span><span class="p">,</span><span class="w"/><span class="s">"1"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"font-size"</span><span class="p">,</span><span class="w"/><span class="s">"18px"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">boolean</span><span class="w"/><span class="n">active</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">locale</span><span class="p">.</span><span class="na">getLanguage</span><span class="p">().</span><span class="na">equalsIgnoreCase</span><span class="p">(</span><span class="n">current</span><span class="p">.</span><span class="na">getLanguage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">applyActiveStyle</span><span class="p">(</span><span class="n">b</span><span class="p">,</span><span class="w"/><span class="n">active</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">b</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">switchLocale</span><span class="p">(</span><span class="n">locale</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Tooltip</span><span class="w"/><span class="p">(</span><span class="n">optional</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">b</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="n">locale</span><span class="p">.</span><span class="na">getLanguage</span><span class="p">().</span><span class="na">toUpperCase</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"/><span class="n">b</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>For the active state, no additional theme variant is deliberately introduced; instead, a minimal style is applied: a slight background tint and an outline ring in the primary colour. Thus, the component remains visually restrained but unambiguous.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">applyActiveStyle</span><span class="p">(</span><span class="n">Button</span><span class="w"/><span class="n">b</span><span class="p">,</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">active</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">active</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">b</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"background"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-primary-color-10pct)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"outline"</span><span class="p">,</span><span class="w"/><span class="s">"2px solid var(--lumo-primary-color-50pct)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">b</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="s">"background"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="s">"outline"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The locale change itself is deliberately implemented &ldquo;robustly&rdquo;. First, the selected locale is matched to a supported language. It is then set in both the session and the UI. The final reload() ensures that all views, dialogues, and components resolve their texts from the resource bundles again. This avoids the additional complexity that would otherwise be caused by LocaleChangeObserver implementations across many components.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">switchLocale</span><span class="p">(</span><span class="n">Locale</span><span class="w"/><span class="n">selected</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">UI</span><span class="w"/><span class="n">ui</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">VaadinSession</span><span class="w"/><span class="n">session</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">VaadinSession</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Locale</span><span class="w"/><span class="n">effective</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">LocaleSelection</span><span class="p">.</span><span class="na">match</span><span class="p">(</span><span class="n">selected</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">LocaleSelection</span><span class="p">.</span><span class="na">setToSession</span><span class="p">(</span><span class="n">session</span><span class="p">,</span><span class="w"/><span class="n">effective</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">session</span><span class="p">.</span><span class="na">setLocale</span><span class="p">(</span><span class="n">effective</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">ui</span><span class="p">.</span><span class="na">setLocale</span><span class="p">(</span><span class="n">effective</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">ui</span><span class="p">.</span><span class="na">getPage</span><span class="p">().</span><span class="na">reload</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Finally, the display of the flags is encapsulated via a small mapping method. This keeps the display in a central place and can be easily adapted later – for example, if US is to be used instead of EN or if other languages are added.</p><p>_—<strong>&lt; IMG DE</strong>&gt; – this is a replacement of the emoji — ___</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private string flagEmoji(Locale locale) {</span></span><span class="line"><span class="cl">  return switch (locale.getLanguage()) {</span></span><span class="line"><span class="cl">    case "de" -&gt; "<span class="nt">&lt;IMG</span><span class="err">DE</span><span class="nt">&gt;</span>";</span></span><span class="line"><span class="cl">    case "fi" -&gt; "<span class="nt">&lt;IMG</span><span class="err">FI</span><span class="nt">&gt;</span>";</span></span><span class="line"><span class="cl">    default -&gt; "<span class="nt">&lt;IMG</span><span class="err">EN</span><span class="nt">&gt;</span>"; EN</span></span><span class="line"><span class="cl">  };</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The process consists of several steps. First, the desired language is compared with the application&rsquo;s supported languages. Then the locale is set in both the session and the current UI. As a result, the new language is immediately available.</p><p>The final page reload ensures that all UI components are re-rendered and that all translations are consistently resolved with the new locale. This approach is deliberately kept simple and avoids additional complexity within the component logic.</p><p>By integrating language switching into the MainLayout, this feature is available in every view of the application. Users can switch languages at any time without leaving the current page.</p><p>This means that the internationalisation of the user interface is fully integrated into the application. Translation resources, locale discovery, session management, and language switching are now intertwined, enabling a multilingual interface with comparatively low implementation effort.</p><h2 id="conclusion-and-outlook">Conclusion and outlook</h2><p>With internationalisation, the URL shortener has reached a point where good user-friendliness and clean architecture converge. The application supports multiple languages without the source code becoming confusing or requiring additional dependencies. Instead, the existing Vaadin Flow mechanisms are relied on, and only added where they make sense for the project.</p><p>The essential building blocks mesh neatly. Translations are stored as resource bundles in the vaadin-i18n directory so that Vaadin recognises them automatically. Clearly structured keys are used to extract texts from the interface and manage them centrally. I18nSupport reduces access to translations to a compact, anywhere-to-use method, keeping UI classes readable.</p><p>The locale determination follows a pragmatic principle. By default, the browser&rsquo;s locale is used, but limited to the languages that are actually supported. This keeps the system predictable and avoids incomplete translation states. At the same time, the selected language is stored on a per-session basis, so that users retain a consistent interface during a session and their selection does not have to be &ldquo;renegotiated&rdquo; with each request.</p><p>Finally, the whole thing becomes particularly visible in the user interface. The language switch in the MainLayout is deliberately minimalist, but on the UX side, it is much more pleasant than a nested menu. Three flag buttons are enough to quickly change the language, and the active state is immediately recognisable. The technical change to the locale is implemented robustly across the session and UI. The final reload is not a workaround, but a conscious design decision: It ensures that all views, dialogues, and components are consistently rendered in the new language without each component having to implement additional observer logic.</p><p>This creates a solid foundation that can be expanded if necessary. An obvious next step would be to add more languages or refine translations without changing the code. For more complex language cases – such as pluralisation, number and date formatting, or language-dependent sentence modules – ICU-based formatting could be added later. It would also be possible to save the language selection not only on a session basis, but also to link it to a user profile permanently.</p><p>For the current state of the URL shortener, however, the solution is deliberately kept to the essentials: It is small enough to remain maintainable, but complete enough to deliver real benefits in everyday life. This is precisely the practical value of good internationalisation – it is hardly noticeable in the code but immediately noticeable to users.</p>
]]></content:encoded><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2026/03/ChatGPT-Image-4.-Marz-2026-17_38_59.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2026/03/ChatGPT-Image-4.-Marz-2026-17_38_59.jpeg"/><enclosure url="https://svenruppert.com/images/2026/03/ChatGPT-Image-4.-Marz-2026-17_38_59.jpeg" type="image/jpeg" length="0"/></item><item><title>Separation of Concerns in Vaadin: Eliminating Inline Styles</title><link>https://svenruppert.com/posts/separation-of-concerns-in-vaadin-eliminating-inline-styles/</link><pubDate>Wed, 04 Mar 2026 13:05:20 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/separation-of-concerns-in-vaadin-eliminating-inline-styles/</guid><description>Vaadin Flow enables the development of complete web applications exclusively in Java. Components, layouts, navigation, and even complex UI structures can be modelled on the server side without working directly with HTML or JavaScript. This approach is one of the main reasons why Vaadin is especially popular in Java-centric projects.</description><content:encoded>&lt;![CDATA[<p>Vaadin Flow enables the development of complete web applications exclusively in Java. Components, layouts, navigation, and even complex UI structures can be modelled on the server side without working directly with HTML or JavaScript. This approach is one of the main reasons why Vaadin is especially popular in Java-centric projects.</p><p>However, as a project grows in size, a typical problem arises: styling is often done directly in Java code. Vaadin allows this conveniently via the getStyle().set(&hellip;) method. This allows you to adjust spacing, colours, or layout properties quickly. In small prototypes, this procedure initially seems harmless and even practical.</p><ol><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#the-problem-with-inline-styling-in-vaadin-flow">The problem with inline styling in Vaadin Flow</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#separation-of-structure-and-presentation-java-vs-css">Separation of structure and presentation: Java vs CSS</a><ol><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#structure-belongs-in-java">Structure belongs in Java.</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#appearance-belongs-in-css">Appearance belongs in CSS.</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#advantages-of-this-separation">Advantages of this separation</a></li></ol></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#replace-inline-styles-with-css-classes">Replace inline styles with CSS classes</a><ol><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#case-in-point">Case in point</a><ol><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#step-1-introduce-a-css-class">Step 1: Introduce a CSS class</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#step-2-using-the-class-in-java-code">Step 2: Using the class in Java code</a></li></ol></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#another-example-from-a-view">Another example from a view</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#benefits-of-this-approach">Benefits of this approach</a></li></ol></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#structure-of-css-files-in-a-vaadin-flow-application">Structure of CSS Files in a Vaadin Flow Application</a><ol><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#the-frontend-directory">The frontend directory</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#integration-of-styles-in-vaadin">Integration of styles in Vaadin</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#separation-of-layout-and-component-styles">Separation of layout and component styles</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#consistent-naming-of-css-classes">Consistent naming of CSS classes</a></li></ol></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#refactoring-real-views-examples-from-the-url-shortener">Refactoring Real Views: Examples from the URL Shortener</a><ol><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#example-1-mainlayout">Example 1: MainLayout</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#example-2-map-layout-in-the-aboutview">Example 2: Map layout in the AboutView</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#example-3-structured-layout-classes">Example 3: Structured layout classes</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#refactoring-as-an-incremental-process">Refactoring as an incremental process</a></li></ol></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#best-practices-for-css-in-vaadin-flow-applications">Best Practices for CSS in Vaadin Flow Applications</a><ol><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#java-describes-structure-css-describes-representation">Java describes structure – CSS describes representation</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#css-classes-instead-of-inline-styles">CSS Classes Instead of Inline Styles</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#leveraging-lumo-design-variables">Leveraging Lumo Design Variables</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#semantic-class-names">Semantic Class Names</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#use-responsive-layouts-consciously">Use responsive layouts consciously</a></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#inline-styles-as-a-deliberately-used-exception">Inline Styles as a Deliberately Used Exception</a></li></ol></li><li><a href="https://svenruppert.com/2026/03/04/separation-of-concerns-in-vaadin-eliminating-inline-styles/#conclusion-of-the-refactoring">Conclusion of the refactoring</a></li></ol><p>In larger applications, however, this practice quickly leads to a code state that is difficult to maintain. Styling information is spread over numerous views and components. Changes to the design have to be made throughout the Java code. Reusability of styles becomes virtually impossible, and the distinction between UI logic and presentation layer becomes increasingly blurred.</p><p>A typical example in many Vaadin projects looks something like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">component</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"padding"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-space-m)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"border-radius"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-border-radius-l)"</span><span class="p">);</span></span></span></code></pre></div></div><p>Such constructs can often be found in almost every view. Over time, a mixture of layout logic, component structure and CSS definitions emerges directly in Java code.</p><p>In the open-source project URL-Shortener, exactly this problem became apparent. Several views contained inline styling, which was originally intended for quick UI adjustments. However, as functionality grew, it became clear that this pattern is not sustainable in the long term.</p><p>The goal of the following refactoring was therefore clearly defined:</p><ul><li>Remove inline styling from views</li><li>Introduction of clear CSS classes</li><li>Clean separation between structure (Java) and presentation (CSS)</li><li>Better maintainability and reusability of the UI</li></ul><p>The article describes step by step how an existing Vaadin UI can be converted from inline styling to a structured CSS architecture. The examples come directly from the real application and show typical situations that occur in many Vaadin projects.</p><p>This is not about making Vaadin completely CSS-driven. Vaadin remains a server-side UI framework. Rather, it is about establishing a clear division of responsibilities: Java defines the structure of the user interface, while CSS handles its visual design.</p><p>This seemingly small change has a significant impact on the maintainability of an application – especially if a Vaadin application is developed over the years.</p><h2 id="the-problem-with-inline-styling-in-vaadin-flow">The problem with inline styling in Vaadin Flow</h2><p>At first glance, the getStyle().set(&hellip;) method in Vaadin Flow seems extremely practical. With just a few lines, spacing can be adjusted, layout problems corrected, or colours changed – directly in the Java code of the respective view. Especially in early project phases or during prototype development, this approach seems quick and uncomplicated.</p><p>In real applications, however, a different development often emerges. As a project grows in size, inline styles spread across multiple views. What were initially only small adjustments gradually became a large number of local layout and styling corrections that are implemented directly in the Java code.</p><p>For example, a typical snippet from a Vaadin application might look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">component</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"padding"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-space-m)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">layout</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"gap"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-space-l)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">button</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"margin-left"</span><span class="p">,</span><span class="w"/><span class="s">"auto"</span><span class="p">);</span></span></span></code></pre></div></div><p>Each of these lines is harmless in itself. Taken together, however, they lead to several structural problems.</p><p><strong>Firstly,</strong> the styling is spread over the entire application. Instead of a central place where the visual appearance is defined, design decisions are spread across many different Java classes. If, for example, the spacing of a layout or the display of certain components is to change later, developers must first find the appropriate places in the code.</p><p><strong>Secondly,</strong> reusability is made more difficult. Many layout patterns – such as map layouts, typical spacing, or alignments – appear multiple times in an application. However, if these are defined as inline styles, they exist again in each view. Changes then have to be made in several places.</p><p><strong>Third,</strong> inline styling blurs the architectural separation between structure and representation. Java classes actually define the structure of the user interface, orchestrate components, and respond to user interactions. When CSS properties are integrated directly into these classes, presentation logic mixes with the UI’s structural definition.</p><p>This problem is particularly evident in larger Vaadin applications developed over a longer period. Small visual adjustments accumulate, and individual getStyle().set(&hellip;) calls, an implicit styling strategy gradually emerges – spread across numerous classes.</p><p>In the open-source project URL-Shortener, exactly this pattern emerged. In several views, including MainLayout, AboutView and CreateView, layout and styling adjustments were made directly in Java code. Although the application remained functionally correct, the UI&rsquo;s visual behaviour became increasingly difficult to understand.</p><p>The refactoring was therefore not just aimed at removing individual style calls. Instead, a clear rule should be introduced:</p><p>Java defines the structure of the user interface – CSS defines its visual design.</p><p>At first, this separation seems like a small organisational change. In practice, however, it significantly improves the application&rsquo;s maintainability. Styles can be defined centrally, reused, and used consistently across multiple views.</p><p>The following sections show step by step how this separation can be implemented in an existing Vaadin Flow application.</p><h2 id="separation-of-structure-and-presentation-java-vs-css">Separation of structure and presentation: Java vs CSS</h2><p>Now that the problems of inline styling in Vaadin Flow have become clear, the central question arises: How can a clean separation be created between the structure of the user interface and its visual representation?</p><p>In classic web applications, this division is clearly defined. HTML describes the structure of the page, CSS controls the visual appearance, and JavaScript handles interactions and logic. In Vaadin Flow, this architecture shifts slightly because the UI structure is defined entirely in Java. Nevertheless, the basic idea remains: structure and presentation should be kept separate.</p><h3 id="structure-belongs-in-java">Structure belongs in Java.</h3><p>Java defines the structure of the user interface in a Vaadin application. These include, in particular:</p><p>the composition of components</p><ul><li>Layout Structures</li><li>Navigation and routing</li><li>Event Handling</li><li>Data Binding</li></ul><p>A typical example is the structure of a layout:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">VerticalLayout</span><span class="w"/><span class="n">container</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">VerticalLayout</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">container</span><span class="p">.</span><span class="k">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">H2</span><span class="p">(</span><span class="s2">"Create new short links"</span><span class="p">),</span><span class="w"/><span class="n">form</span><span class="p">,</span><span class="w"/><span class="n">actions</span><span class="p">);</span></span></span></code></pre></div></div><p>Here, the code describes only the structure of the interface: which components are present and how they are arranged.</p><h3 id="appearance-belongs-in-css">Appearance belongs in CSS.</h3><p>The visual representation of the components, on the other hand, should be defined via CSS. These include, but are not limited to:</p><ul><li>Spacing and padding</li><li>Colours and backgrounds</li><li>Shadows and Borders</li><li>Typography</li><li>responsive layout adjustments</li></ul><p>For example, instead of setting padding directly in Java code</p><p><strong>component.getStyle().set(&ldquo;padding&rdquo;, &ldquo;var(&ndash;lumo-space-m)&rdquo;);</strong></p><p>A CSS class is used:</p><p><strong>component.addClassName(&ldquo;card&rdquo;);</strong></p><p>The actual visual definition is then in the stylesheet:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="p">.</span><span class="na">card</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">padding</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">space</span><span class="o">-</span><span class="n">m</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">border</span><span class="o">-</span><span class="n">radius</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">border</span><span class="o">-</span><span class="n">radius</span><span class="o">-</span><span class="n">l</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This division ensures that layout decisions can be managed centrally rather than spread across several views.</p><h3 id="advantages-of-this-separation">Advantages of this separation</h3><p>The consistent separation between Java structure and CSS presentation has several advantages.</p><p>First, the Java code will be much more readable. Views focus on the composition of the UI components without being overloaded by styling details.</p><p>Second, styles can be centrally managed and reused. Once a CSS class has been defined, it can be used across different views without layout rules repeating in the code.</p><p>Third, the application&rsquo;s maintainability is improved. Changes to the design do not require adjustments in numerous Java classes, but can be made centrally in the stylesheet.</p><p>Especially in larger Vaadin projects with several developers, it quickly becomes apparent how important this clear division of responsibility is.</p><p>In the next section, we will use concrete examples from the application to show how existing inline styles can be replaced by CSS classes step by step.</p><h2 id="replace-inline-styles-with-css-classes">Replace inline styles with CSS classes</h2><p>Once the basic separation between structure (Java) and presentation (CSS) has been defined, a practical question arises in existing Vaadin applications: How can an existing codebase be gradually converted from inline styling to CSS classes?</p><p>In practice, the changeover usually takes place incrementally. Instead of refactoring the entire application at once, individual views or components are cleaned up first. Direct style definitions in the Java code are removed and replaced by CSS classes.</p><h3 id="case-in-point">Case in point</h3><p>A common pattern in Vaadin views is to control layout behaviour directly via inline styles. An example from the application is moving a component to the right end of a layout.</p><p>For example, before refactoring, the code looked like this:</p><p><strong>storeIndicator.getStyle().set(&ldquo;margin-left&rdquo;, &ldquo;auto&rdquo;);</strong></p><p>The code serves its purpose: the component is moved to the right. The problem, however, is that this layout rule is now firmly anchored in Java code.</p><h4 id="step-1-introduce-a-css-class">Step 1: Introduce a CSS class</h4><p>Instead of setting the Style property directly, a suitable CSS class is first defined. This is typically located in the application&rsquo;s global stylesheet file.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="p">.</span><span class="na">mainlayout</span><span class="o">-</span><span class="n">right</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">margin</span><span class="o">-</span><span class="n">left</span><span class="p">:</span><span class="w"/><span class="n">auto</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The advantage of this solution is that the layout rule is now centrally defined and can be reused by several components.</p><h4 id="step-2-using-the-class-in-java-code">Step 2: Using the class in Java code</h4><p>The next step is to remove the inline style in the Java code and replace it with the CSS class.</p><p><strong>storeIndicator.addClassName(&ldquo;mainlayout-right&rdquo;);</strong></p><p>This means that the Java code remains solely responsible for the UI&rsquo;s structure. The component&rsquo;s visual alignment is controlled entirely by CSS.</p><h3 id="another-example-from-a-view">Another example from a view</h3><p>Larger layout definitions can also be cleaned up in this way. In many Vaadin views, for example, constructs such as:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">container</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"border-radius"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-border-radius-l)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"gap"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-space-l)"</span><span class="p">);</span></span></span></code></pre></div></div><p>These style definitions can be converted into a CSS class:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="p">.</span><span class="na">card</span><span class="o">-</span><span class="n">container</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">border</span><span class="o">-</span><span class="n">radius</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">border</span><span class="o">-</span><span class="n">radius</span><span class="o">-</span><span class="n">l</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">gap</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">space</span><span class="o">-</span><span class="n">l</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In Java code, the definition is then reduced to:</p><p><strong>container.addClassName(&ldquo;card-container&rdquo;);</strong></p><h3 id="benefits-of-this-approach">Benefits of this approach</h3><p>This refactoring results in several improvements.</p><p><strong>First</strong> , the Java code will be much more compact and easier to read. Layout details disappear from the View classes.</p><p><strong>Second</strong> , styles can be managed centrally. Layout changes only need to be made at one point in the stylesheet.</p><p><strong>Third</strong> , it creates a consistent visual language within the application by leveraging reusable CSS classes.</p><p>The migration of inline styles to CSS classes is therefore a central step in keeping Vaadin applications maintainable in the long term.</p><p>The next chapter shows where CSS files are stored in a Vaadin Flow application and how they are correctly integrated.</p><h2 id="structure-of-css-files-in-a-vaadin-flow-application">Structure of CSS Files in a Vaadin Flow Application</h2><p>Now that inline styles have been removed from the Java code and replaced with CSS classes, the next important question arises: Where should these CSS definitions be stored in a Vaadin flow application?</p><p>Vaadin Flow is internally based on Web Components and uses the application&rsquo;s frontend directory to provide static resources such as stylesheets, JavaScript, or images. For CSS, this results in a clear structure that has proven itself in practice.</p><h3 id="the-frontend-directory">The frontend directory</h3><p>In a typical Vaadin application, there is a directory called frontend. This contains all resources that are loaded directly by the browser.</p><p>For example, a commonly used structure looks like this:</p><p><strong>frontend/</strong></p><p>** styles/**</p><p>** main.css**</p><p>** layout.css**</p><p>** components.css**</p><p>Within this folder, the styles can be logically organised by area of responsibility.</p><ul><li>main.css contains basic styles of the application</li><li>layout.css defines layout rules</li><li>components.css includes reusable component styles</li></ul><p>This splitting prevents all styles from ending up in a single file and facilitates long-term maintenance.</p><h3 id="integration-of-styles-in-vaadin">Integration of styles in Vaadin</h3><p>For the CSS files to take effect in the application, they must be loaded by Vaadin. This is typically done via annotations such as @CssImport.</p><p>An example in a central layout class might look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@CssImport</span><span class="p">(</span><span class="s">"./styles/main.css"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainLayout</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">AppLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Once this annotation is in place, the styles defined in it become available throughout the application.</p><p>Alternatively, you can use a global theme, in which styles are loaded automatically. In this case, the CSS files are located in the directory, for example:</p><p><strong>frontend/themes/<theme-name>/</strong></p><p>This approach is particularly recommended in larger applications, as the theme serves as a central point for all design decisions.</p><h3 id="separation-of-layout-and-component-styles">Separation of layout and component styles</h3><p>Another best practice is to distinguish between layout styles and component styles.</p><p>Layout styles describe, for example:</p><ul><li>Container spacing</li><li>Grid or Flex Layouts</li><li>responsive behavior</li></ul><p>Component styles, on the other hand, define the visual appearance of reusable UI elements such as cards, badges, or toolbars.</p><p>This separation prevents layout rules and visual component logic from being mixed.</p><h3 id="consistent-naming-of-css-classes">Consistent naming of CSS classes</h3><p>To ensure that CSS classes remain understandable in the long term, a consistent naming convention should be used.</p><p>One possible pattern is based on the respective view or component:</p><p><strong>.about-card</strong></p><p><strong>.about-hero</strong></p><p><strong>.mainlayout-right</strong></p><p><strong>.searchbar-container</strong></p><p>This makes it immediately recognisable which area of the application a certain style belongs to.</p><p>A clearly structured CSS organisation is the basis for ensuring that the previously introduced separation between Java and CSS is permanent.</p><p>While Java continues to define the structure and behaviour of the user interface, the application&rsquo;s visual appearance is centrally controlled via stylesheets. This reduces redundancies, facilitates design changes, and ensures a consistent user interface across all views.</p><p>The next chapter shows how concrete refactorings in the application were implemented and which patterns proved particularly helpful.</p><h2 id="refactoring-real-views-examples-from-the-url-shortener">Refactoring Real Views: Examples from the URL Shortener</h2><p>The principles described so far – the separation of structure and presentation, as well as the replacement of inline styles with CSS classes – can best be understood through concrete examples. In the open-source project URL-Shortener, several views were gradually refactored to implement this separation.</p><p>This chapter shows how existing Vaadin code has been adapted and which patterns have proven to be particularly practical.</p><h3 id="example-1-mainlayout">Example 1: MainLayout</h3><p>In the MainLayout, an inline style was originally used to move an element to the right within a layout.</p><p>Before refactoring, the code looked like this:</p><p><strong>storeIndicator.getStyle().set(&ldquo;margin-left&rdquo;, &ldquo;auto&rdquo;);</strong></p><p>This solution works technically flawlessly, but leads to layout rules being defined directly in Java code.</p><p>After the refactoring, a CSS class was introduced instead.</p><p>Java:</p><p><strong>storeIndicator.addClassName(&ldquo;layout-spacer-right&rdquo;);</strong></p><p>CSS:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="p">.</span><span class="na">layout</span><span class="o">-</span><span class="n">spacer</span><span class="o">-</span><span class="n">right</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">margin</span><span class="o">-</span><span class="n">left</span><span class="p">:</span><span class="w"/><span class="n">auto</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The layout rule is now exclusively in the stylesheet, while the Java code only contains the structural information that this component has a specific layout role.</p><h3 id="example-2-map-layout-in-the-aboutview">Example 2: Map layout in the AboutView</h3><p>The AboutView uses multiple containers that are visually represented as maps. Originally, these layout rules were defined directly via style calls.</p><p>Typical inline code looked something like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">card</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"background"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-base-color)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"border-radius"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-border-radius-l)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"box-shadow"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-box-shadow-s)"</span><span class="p">);</span></span></span></code></pre></div></div><p>These styles were then converted into a reusable CSS class.</p><p>CSS:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="p">.</span><span class="na">card</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">background</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">base</span><span class="o">-</span><span class="n">color</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">border</span><span class="o">-</span><span class="n">radius</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">border</span><span class="o">-</span><span class="n">radius</span><span class="o">-</span><span class="n">l</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">box</span><span class="o">-</span><span class="n">shadow</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">box</span><span class="o">-</span><span class="n">shadow</span><span class="o">-</span><span class="n">s</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">padding</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">space</span><span class="o">-</span><span class="n">l</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Java:</p><p><strong>card.addClassName(&ldquo;card&rdquo;);</strong></p><p>The advantage is that all cards in the application now have a consistent appearance, and changes can be made centrally.</p><h3 id="example-3-structured-layout-classes">Example 3: Structured layout classes</h3><p>Another pattern is to name layout roles via CSS classes explicitly. Instead of generic styles, semantic classes are introduced to describe an element&rsquo;s purpose.</p><p>Examples include:</p><p><strong>.searchbar-container</strong></p><p><strong>.bulk-actions-bar</strong></p><p><strong>.overview-grid</strong></p><p>Such classes make it easier to understand the application&rsquo;s CSS layout.</p><h3 id="refactoring-as-an-incremental-process">Refactoring as an incremental process</h3><p>It is important to note that this change need not occur in a single step. In practice, an incremental approach has proven to be effective.</p><ul><li>New components are developed directly with CSS classes</li><li>Existing inline styles will be refactored when the opportunity arises</li><li>Recurring style patterns are gradually centralised</li></ul><p>In this way, an existing Vaadin application can be converted into a cleanly structured CSS architecture without major risks.</p><p>The examples from the URL shortener show that even small refactorings can have a significant impact on an application&rsquo;s maintainability. Removing inline styles makes the Java code clearer, while the visual design can be maintained centrally in the stylesheet.</p><p>The next chapter identifies the general best practices that can be derived from this refactoring and how to apply them in future Vaadin projects.</p><h2 id="best-practices-for-css-in-vaadin-flow-applications">Best Practices for CSS in Vaadin Flow Applications</h2><p>After the refactoring of the existing views is complete, the question arises: which general rules can be derived from this? In practice, it has been shown that a few simple principles are already sufficient to keep Vaadin Flow applications maintainable and consistent in the long term.</p><p>This chapter summarises the most important best practices that have proven themselves during the conversion of the URL Shortener project.</p><h3 id="java-describes-structure--css-describes-representation">Java describes structure – CSS describes representation</h3><p>The most important rule is also the simplest: Java defines the structure of the user interface, CSS defines its visual design.</p><p>In Vaadin, the entire UI composition is modelled in Java. Layout containers, components and their hierarchy therefore clearly belong in the Java code.</p><p>Examples of structural definitions in Java include:</p><ul><li>Layout containers (VerticalLayout, HorizontalLayout, FormLayout)</li><li>Component Structure</li><li>Event Handling</li><li>Data Binding</li></ul><p>Visual aspects, on the other hand, should be regulated via CSS. These include, in particular:</p><ul><li>Spacing</li><li>Colors</li><li>Shadows</li><li>Typography</li><li>visual highlights</li></ul><p>This clear separation prevents UI logic and presentation details from being mixed in the same code.</p><h3 id="css-classes-instead-of-inline-styles">CSS Classes Instead of Inline Styles</h3><p>Inline styles should only be used in exceptional cases. Instead, it is recommended to define consistent CSS classes and bind them to components via addClassName().</p><p>Example:</p><p><strong>component.addClassName(&ldquo;card&rdquo;);</strong></p><p>The visual definition is then done in the stylesheet:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="p">.</span><span class="na">card</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">padding</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">space</span><span class="o">-</span><span class="n">m</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">border</span><span class="o">-</span><span class="n">radius</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">border</span><span class="o">-</span><span class="n">radius</span><span class="o">-</span><span class="n">l</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This approach ensures that layout rules can be maintained centrally.</p><h3 id="leveraging-lumo-design-variables">Leveraging Lumo Design Variables</h3><p>Vaadin uses Lumo, a consistent design system that provides numerous CSS variables.</p><p>Typical examples are:</p><ul><li><strong>--lumo-space-m</strong></li><li><strong>--lumo-border-radius-l</strong></li><li><strong>--lumo-box-shadow-s</strong></li><li><strong>--lumo-primary-color</strong></li></ul><p>These variables should be used preferably, as they automatically harmonise with the chosen theme and ensure consistent spacing and colours.</p><p>An example:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="p">.</span><span class="na">card</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">padding</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">space</span><span class="o">-</span><span class="n">l</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">border</span><span class="o">-</span><span class="n">radius</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">lumo</span><span class="o">-</span><span class="n">border</span><span class="o">-</span><span class="n">radius</span><span class="o">-</span><span class="n">l</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="semantic-class-names">Semantic Class Names</h3><p>CSS classes should not only describe how something looks, but what role an element plays in the layout.</p><p>Less helpful would be, for example, names like:</p><ul><li><strong>.box1</strong></li><li><strong>.red-border</strong></li><li><strong>.container-large</strong></li></ul><p>Semantic names that are based on views or components are better:</p><ul><li><strong>.about-card</strong></li><li><strong>.searchbar-container</strong></li><li><strong>.bulk-actions-bar</strong></li><li><strong>.overview-grid</strong></li></ul><p>This means that even in larger style sheets, it remains clear to which area of the application a style belongs.</p><h3 id="use-responsive-layouts-consciously">Use responsive layouts consciously</h3><p>Vaadin already provides many mechanisms for responsive layouts, including FormLayout, FlexLayout, and SplitLayout.</p><p>For example, FormLayouts can be configured directly via Java:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">form</span><span class="p">.</span><span class="na">setResponsiveSteps</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">new</span><span class="w"/><span class="n">FormLayout</span><span class="p">.</span><span class="na">ResponsiveStep</span><span class="p">(</span><span class="s">"0"</span><span class="p">,</span><span class="w"/><span class="n">1</span><span class="p">),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">new</span><span class="w"/><span class="n">FormLayout</span><span class="p">.</span><span class="na">ResponsiveStep</span><span class="p">(</span><span class="s">"900px"</span><span class="p">,</span><span class="w"/><span class="n">2</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">);</span></span></span></code></pre></div></div><p>These structural layout decisions belong in the Java code by design, as they are part of the UI composition. CSS then only complements the visual behaviour.</p><h3 id="inline-styles-as-a-deliberately-used-exception">Inline Styles as a Deliberately Used Exception</h3><p>Despite all the recommendations, there are situations in which inline styles can be useful.</p><p>Typical examples are:</p><ul><li>dynamically calculated layout values</li><li>short-term UI adjustments</li><li>experimental prototypes</li></ul><p>However, it is important that these cases remain the exception and do not become the default style of an application.</p><p>The combination of a clear division of responsibility, reusable CSS classes and consistent design variables results in a much more maintainable Vaadin application.</p><p>Especially in projects developed over a longer period, this structure quickly pays off. Changes to the visual design can be implemented centrally, while the Java code continues to describe only the user interface&rsquo;s structure and behaviour.</p><h2 id="conclusion-of-the-refactoring">Conclusion of the refactoring</h2><p>The switch from inline styling to a clearly structured CSS architecture may seem like a small cosmetic change at first glance. In practice, however, it quickly becomes apparent that this refactoring has a profound impact on the maintainability and comprehensibility of a Vaadin Flow application.</p><p>In the original state of the application, many layout and display details were anchored directly in the Java code of the views. Method calls such as getStyle().set(&hellip;) provided quick UI adjustments in the short term but, in the long term, led to a mixing of structure and presentation. The result was a codebase in which design decisions were spread across numerous classes.</p><p>With the introduction of CSS classes and the consistent outsourcing of visual properties to style sheets, this mixing has been broken up again. Java now only describes the structure of the user interface: components, layout containers, navigation and interactions. The visual design, on the other hand, is defined centrally via CSS.</p><p>This separation brings several immediate benefits.</p><p><strong>First,</strong> the Java code&rsquo;s readability improves significantly. Views focus on the user interface&rsquo;s composition without being overwhelmed by layout details. Developers can more quickly understand which structure a view has and which components interact with each other.</p><p><strong>Second,</strong> the application&rsquo;s design becomes more consistent. Reusable CSS classes ensure that similar components in different views are also visually identical. Changes to the appearance can be made centrally in the stylesheet without adapting numerous Java classes.</p><p><strong>Thirdly,</strong> this structure facilitates team collaboration. Developers can focus on the Java side of the application, while design adjustments can be made independently in CSS. Especially in larger projects, this reduces conflicts and simplifies further UI development.</p><p>The refactoring of the URL shortener project has shown that even relatively small changes to the style structure can have a big impact on long-term maintainability. Especially in applications developed over the years, a clear separation between structure and presentation quickly pays off.</p><p>Vaadin Flow remains a server-side UI framework that puts Java at the centre of UI development. Nevertheless, such an architecture also benefits significantly from the classic principles of web development: structure, presentation, and behaviour should be clearly separated.</p><p>If you apply this basic rule consistently, you will get a Vaadin application that is not only functionally stable but also remains understandable and maintainable in the long term.</p>
]]></content:encoded><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2026/03/ChatGPT-Image-4.-Marz-2026-13_01_05.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2026/03/ChatGPT-Image-4.-Marz-2026-13_01_05.jpeg"/><enclosure url="https://svenruppert.com/images/2026/03/ChatGPT-Image-4.-Marz-2026-13_01_05.jpeg" type="image/jpeg" length="0"/></item><item><title>An unexpectedly hassle-free upgrade</title><link>https://svenruppert.com/posts/an-unexpectedly-hassle-free-upgrade/</link><pubDate>Mon, 16 Feb 2026 11:22:56 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/an-unexpectedly-hassle-free-upgrade/</guid><description>The starting point for this article was not a strategic architecture workshop or a long-term planned migration path, but a comparatively unspectacular step: updating the version numbers in the existing project. As part of further developing my URL shortener project, a regular dependency update was due anyway. Vaadin 25 (was already available at that time, so it made sense to proceed.</description><content:encoded>&lt;![CDATA[<p>The starting point for this article was not a strategic architecture workshop or a long-term planned migration path, but a comparatively unspectacular step: updating the version numbers in the existing project. As part of further developing my URL shortener project, a regular dependency update was due anyway. Vaadin 25 (was already available at that time, so it made sense to proceed.</p><p><strong>The source code for the project can be found and is</strong></p><p><strong>available under</strong><a href="https://3g3.eu/url">https://3g3.eu/url</a></p><p>The expectations were realistic, perhaps even slightly sceptical. A new major release, new minimum requirements, a modernised stack – all of these are usually good reasons to expect at least minor adjustments or initial friction. Accordingly, there was little hope that simply checking the version numbers would be sufficient.</p><ol><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#why-vaadin-25-is-more-than-a-version-leap">Why Vaadin 25 is more than a version leap</a></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#the-new-minimum-standard-java-21-jakarta-ee-and-modern-platforms">The new minimum standard: Java 21, Jakarta EE and modern platforms</a></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#leaner-stack-faster-builds-and-less-ballast">Leaner stack, faster builds and less ballast.</a></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#styling-theming-rethought-css-instead-of-special-logic">Styling &amp; Theming Rethought: CSS Instead of Special Logic</a><ol><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#before-and-after-styling-in-practice">Before and after: styling in practice</a><ol><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#example-1-button-styling">Example 1: Button styling</a></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#example-2-layout-spacing-and-consistency">Example 2: Layout Spacing and Consistency</a></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#example-3-dark-light-theme-without-special-logic">Example 3: Dark/Light theme without special logic</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#component-and-rendering-improvements-in-everyday-life">Component and rendering improvements in everyday life</a><ol><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#before-and-after-rendering-and-overlay-behaviour-in-practice">Before and After: Rendering and Overlay Behaviour in Practice</a><ol><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#example-1-nested-dialogues">Example 1: Nested dialogues</a></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#example-2-focus-return-after-dialogue-closure">Example 2: Focus return after dialogue closure</a></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#example-3-overlay-over-grid-and-scroll-container">Example 3: Overlay over Grid and Scroll Container</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#element-api-svg-and-mathml-technical-uis-directly-from-java">Element API, SVG and MathML: Technical UIs directly from Java</a><ol><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#before-and-after-technical-visualisation-with-and-without-element-api">Before and After: Technical Visualisation with and without Element API</a><ol><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#example-1-status-indicator-as-svg">Example 1: Status indicator as SVG</a></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#example-2-simple-bar-chart">Example 2: Simple Bar Chart</a></li><li><a href="https://svenruppert.com/2026/02/16/an-unexpectedly-hassle-free-upgrade/#example-3-representation-of-a-formula-with-mathml">Example 3: Representation of a Formula with MathML</a></li></ol></li></ol></li></ol><p>However, that was exactly the case. After adapting the Vaadin version and the current Java and platform dependencies, the project could be built, started, and operated without further changes. The application behaved as before: views were rendered correctly, dialogues worked, styles were intact, and even more complex UI flows showed no abnormalities.</p><p>This result was remarkable in that it was not based on a super trivial demo project. The URL shortener is a mature application with a clear module structure, server-side UI logic, security mechanisms and a small number of UI components. The fact that a major upgrade was possible under these conditions without immediate follow-up work is not routine.</p><figure><img src="/images/2026/02/image-5-1024x632.png" alt="Screenshot of a URL Shortener dashboard overview showing a list of shortcodes, corresponding URLs, creation dates, active status, expiry information, and action options like Import and Export." loading="lazy" decoding="async"/><p>It was precisely this experience that triggered a closer look at Vaadin 25. If a framework upgrade of this magnitude works right away, the question inevitably arises: why? What decisions in the substructure make this possible? Where was stability deliberately focused? And where are the changes lurking that only become relevant on closer inspection?</p><h2 id="why-vaadin-25-is-more-than-a-version-leap">Why Vaadin 25 is more than a version leap</h2><p>Vaadin 25 does not mark a classic evolutionary step with a collection of new widgets or API extensions, but a deliberate realignment of the framework. While previous major releases often featured additional features or incremental improvements, Vaadin 25 focuses on consolidation: fewer special logic elements, less legacy, and closer alignment with established standards in modern Java and web development.</p><p>This realignment is a direct response to the reality of many productive enterprise applications. Today, long-running web UIs must not only be functional but also maintainable, secure, and easy to integrate for years. This is exactly where Vaadin 25 comes in. The framework views itself less as an isolated ecosystem with its own rules and more as an integral part of a contemporary Java stack.</p><p>A central signal for this is the clear commitment to modern platforms. With Java 21 as the minimum requirement and the focus on current Jakarta and Spring versions, Vaadin is deliberately positioning itself where the rest of the enterprise Java stack is moving. Older compatibility promises are abandoned in favour of creating a clean, consistent technological underpinning.</p><p>Vaadin 25 is also a turning point conceptually. Many mechanisms that were once considered Vaadin-typical are now being questioned or simplified. Styling follows more classic CSS principles, build processes are approaching the standard Java workflow, and client-side code is becoming leaner and more transparent. The result is a framework that requires less explanation and integrates more seamlessly into existing development and operational processes.</p><p>Vaadin 25 is therefore not a release that primarily stands out for visible UI innovations. Its added value lies in its long-term perspective: more stable foundations, reduced complexity, and better integration with modern Java architectures. For developers and architects, this means one thing above all: In the future, decisions for or against Vaadin can be made more clearly along technical criteria – and less along framework specifics.</p><h2 id="the-new-minimum-standard-java-21-jakarta-ee-and-modern-platforms">The new minimum standard: Java 21, Jakarta EE and modern platforms</h2><p>With Vaadin 25, the framework draws a clear line and defines a new minimum standard for technology. This decision was made deliberately and is aimed at projects to be operated in the long term, using current Java platforms. Backward compatibility at all costs is abandoned in favour of clarity, consistency, and maintainability.</p><p>The focus is on Java 21 as a binding basis. Vaadin thus relies on the current LTS version, which brings both linguistic and runtime improvements. For Vaadin applications, this means not only access to modern language constructs but also a common denominator across UI code, backend logic, and tests. The days when UI code remained at an older language level for compatibility are over.</p><p>Closely linked to this is the alignment with current Jakarta standards. Vaadin 25 is based on Jakarta EE 11 and Servlet 6.1, and finally says goodbye to historical javax dependencies. At first glance, this change may seem like a purely technical detail. Still, in practice, it has noticeable effects: libraries, containers, and security mechanisms now share a consistent namespace and can be integrated more cleanly. Especially in safety-relevant applications, this reduces friction losses and misconfigurations.</p><p>This line will also be consistently continued in the Spring environment. Vaadin 25 is geared toward Spring Boot 4 and Spring Framework 7 and does not support earlier versions. This eliminates a significant amount of compatibility code required in previous versions. At the same time, Vaadin applications are moving closer to the mainstream Spring stack, which simplifies operation, monitoring and security integration.</p><p>The new minimum standard also affects the entire build and toolchain area. Modern Maven and Gradle plugins, current Node versions for the frontend, and a clearer separation between development and production artefacts make Vaadin projects feel more like classic Java applications. Special profiles and project-specific workarounds are becoming less important.</p><p>The perspective is important: Vaadin 25 requires more discipline at the start but reduces complexity over the long term. Projects planning to migrate to Java 21 or that have already migrated to Java 21 and current platforms will benefit directly. For older applications, on the other hand, the upgrade is a deliberate strategic decision – with initial effort but a much more stable foundation for the coming years.</p><h2 id="leaner-stack-faster-builds-and-less-ballast">Leaner stack, faster builds and less ballast.</h2><p>One of the most noticeable effects of Vaadin 25 is not immediately visible in the UI; it is part of the project&rsquo;s underlying architecture. The framework has been specifically streamlined in this version: transitive dependencies have been reduced, historical compatibility layers have been removed, and build processes have been simplified. The result is a stack that is much closer to a classic Java server application.</p><p>In previous Vaadin versions, it was not uncommon for projects to carry a significant amount of indirect dependencies. Many of these were necessary to support different platform versions, old browser strategies or alternative operating modes. With Vaadin 25, much of this ballast is eliminated. The dependencies are more clearly structured, more transparently comprehensible and overall more manageable.</p><p>This reduction directly affects the build process. Both Maven and Gradle builds benefit from shorter resolution times and less special configuration. In particular, the gap between development and production build has narrowed. Vaadin applications behave much more strongly than ordinary Java artefacts, making it easier for new developers to get started and simplifying CI pipelines.</p><p>Startup times in development mode also benefit from this approach. Fewer initialisation steps, clearer separation between server and client components, and modernised front-end dependencies result in a faster feedback loop. Especially in larger projects, where frequent restarts are part of the daily routine, this productivity gain should not be underestimated.</p><p>Another aspect is resource consumption during operations. A leaner stack does not automatically mean minimal memory usage, but it does reduce unnecessary load. Fewer libraries mean fewer classes, fewer potential conflicts, and a smaller attack surface. For production environments – especially in safety-critical or highly regulated contexts – this is a clear advantage.</p><p>The key thing is the demarcation: Vaadin 25 is not a minimal framework. Rather, it is about conscious reduction. Everything that is no longer necessary will be removed or simplified. This attitude runs through the entire stack and is a direct continuation of the repositioning described in the previous chapters.</p><p>The leaner stack thus forms an essential basis for further innovations in Vaadin 25. Simplified styling, more stable UI mechanisms and clearer migration paths are only possible because the framework frees itself from legacy burdens that have grown over the years.</p><h2 id="styling--theming-rethought-css-instead-of-special-logic">Styling &amp; Theming Rethought: CSS Instead of Special Logic</h2><p>The styling model is one of the areas in which Vaadin 25 stands out most clearly from previous versions. While Vaadin has long brought its own concepts and abstractions around themes, variants, and style hooks, version 25 follows a clear course: CSS is understood as an equal, direct design mechanism – without unnecessary framework-specific paths.</p><p>This decision is less cosmetic than architectural. In many projects, the previous styling model required in-depth knowledge of Vaadin to make UI adjustments. At the same time, existing web know-how could only be used to a limited extent. Vaadin 25 specifically reduces this friction by aligning styling more closely with established web standards, making it easier to understand and maintain.</p><p>A visible result of this approach is the new default theme Aura. It does not replace Lumo, but sets a different focus. Aura looks calmer, more modern, and less playful without pushing itself to the forefront. For productive applications, this means a consistent look and feel without extensive customisation. Lumo remains available and is particularly suitable for projects that already build heavily on it or deliberately pursue a different visual profile.</p><p>In addition to the visual basis, flexibility is crucial. Vaadin 25 makes it possible to change themes dynamically – depending on user roles, clients or environments, for example. Light and dark variants can be realised without far-reaching modifications. This capability is not new, but it can be implemented more cleanly and comprehensibly with the simplified styling model.</p><p>The material theme was deliberately removed. This decision underscores the new focus: Instead of maintaining several design languages in parallel, Vaadin focuses on a few well-integrated core themes. For projects that require a highly individualised interface, this is not a disadvantage. On the contrary, a stronger CSS focus allows your design systems to be implemented more consistently and more framework-agnostic.</p><p>The effect on team collaboration is also important. Designers and front-end developers can work much more directly with Vaadin 25 without having to familiarise themselves with Vaadin-specific styling concepts. At the same time, Java developers retain full control over the UI’s structure and behaviour.</p><h3 id="before-and-after-styling-in-practice">Before and after: styling in practice</h3><p>To make the difference tangible, it is worth taking a direct look at typical styling approaches before and after Vaadin 25. The examples are deliberately simplified but clearly demonstrate the paradigm shift.</p><h4 id="example-1-button-styling">Example 1: Button styling</h4><p><strong>Before (classic Vaadin approach with theme variants and Java hooks):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">save</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Save"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">save</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">save</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"background-color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-success-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">save</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"color"</span><span class="p">,</span><span class="w"/><span class="s">"white"</span><span class="p">);</span></span></span></code></pre></div></div><p>The styling here is partly anchored in the Java code. Colours and visual details are controlled via Vaadin-specific variables and APIs, which blur the separation between structure and representation.</p><p><strong>After (Vaadin 25, CSS-first):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">save</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Save"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">save</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"btn-save"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="na">btn</span><span class="o">-</span><span class="n">save</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">background</span><span class="o">-</span><span class="n">color</span><span class="p">:</span><span class="w"/><span class="n">var</span><span class="p">(</span><span class="o">--</span><span class="n">vaadin</span><span class="o">-</span><span class="n">color</span><span class="o">-</span><span class="n">success</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">colour</span><span class="p">:</span><span class="w"/><span class="n">white</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Responsibility is clearly separated: Java describes the structure and semantics, while CSS handles the visual appearance. The styling is easy to find, testable and customizable independently of the Java code.</p><h4 id="example-2-layout-spacing-and-consistency">Example 2: Layout Spacing and Consistency</h4><p><strong>Before (inline styles in code):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">VerticalLayout</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">VerticalLayout</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">layout</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"padding"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-space-m)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">layout</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"gap"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-space-s)"</span><span class="p">);</span></span></span></code></pre></div></div><p>Such constructs are functional, but quickly lead to scattered styling knowledge in the code.</p><p><strong>After (CSS-based layout classes):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">VerticalLayout</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">VerticalLayout</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">layout</span><span class="p">.</span><span class="na">addClassName</span><span class="p">(</span><span class="s">"content-layout"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="na">content</span><span class="o">-</span><span class="n">layout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">padding</span><span class="p">:</span><span class="w"/><span class="n">1rem</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Gap</span><span class="p">:</span><span class="w"/><span class="n">0</span><span class="p">.</span><span class="na">5rem</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Spacing and layout rules are now centrally defined and can be adapted consistently throughout the project.</p><h4 id="example-3-darklight-theme-without-special-logic">Example 3: Dark/Light theme without special logic</h4><p><strong>Before (Vaadin-specific theme switch):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">getElement</span><span class="p">().</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"theme"</span><span class="p">,</span><span class="w"/><span class="s">"dark"</span><span class="p">);</span></span></span></code></pre></div></div><p><strong>After (Vaadin 25, CSS-oriented):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">getElement</span><span class="p">().</span><span class="na">getClassList</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"theme-dark"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="na">theme</span><span class="o">-</span><span class="n">dark</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="o">--</span><span class="n">vaadin</span><span class="o">-</span><span class="n">color</span><span class="o">-</span><span class="n">background</span><span class="p">:</span><span class="w"/><span class="err">#</span><span class="n">1e1e1e</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="o">--</span><span class="n">vaadin</span><span class="o">-</span><span class="n">color</span><span class="o">-</span><span class="n">text</span><span class="p">:</span><span class="w"/><span class="err">#</span><span class="n">f5f5f5</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Changing the theme is now a clearly defined CSS mechanism that can be easily integrated with existing design systems or user preferences.</p><p>These examples illustrate the core of the change: Vaadin 25 consistently shifts styling decisions to their proper place. The code becomes clearer, the styling more flexible, and collaboration among backend, frontend, and design roles much more relaxed.</p><h2 id="component-and-rendering-improvements-in-everyday-life">Component and rendering improvements in everyday life</h2><p>While many changes in Vaadin 25 are structural, the improvements to components and rendering are concrete in day-to-day UI work. In particular, dialogues, overlays, menus, and other floating UI elements benefit from a revised architecture designed for consistency, predictability, and stability.</p><p>In previous Vaadin versions, problems with overlaps, loss of focus or unexpected Z-index behaviour were not uncommon – especially in more complex applications with nested dialogues or dynamically reloaded components. Such effects could usually be remedied, but required additional logic, workarounds or a deep understanding of internal Vaadin mechanisms.</p><p>Vaadin 25 relies on a unified overlay model here. Dialogues, context menus, tooltips and similar components now follow a common rendering strategy. As a result, they are more consistently positioned, respond more stably to size changes, and behave more predictably when interacting with one another. For developers, this means fewer surprises and less special treatment in the code.</p><p>A particularly relevant aspect is focus management. Keyboard navigation, focus restoration after closing a dialogue, and simultaneous interaction with multiple overlays are much more robust in Vaadin 25. This not only improves the user experience but also enhances accessibility and testability.</p><p>The interaction with modern layouts also benefits from the new rendering logic. Overlays adapt better to viewport changes, respond cleanly to scroll containers, and retain their position even during dynamic UI updates. Especially in data-driven applications with many interactions, this reduces visual artefacts and inconsistencies.</p><p>It is important to note that these improvements hardly require any new APIs. Existing code often benefits from the upgrade itself. At the same time, previously necessary protective measures or auxiliary constructs become superfluous, simplifying the code and making it easier to read.</p><h3 id="before-and-after-rendering-and-overlay-behaviour-in-practice">Before and After: Rendering and Overlay Behaviour in Practice</h3><p>The differences between previous Vaadin versions and Vaadin 25 are particularly evident when examining typical UI scenarios in real-world applications.</p><h4 id="example-1-nested-dialogues">Example 1: Nested dialogues</h4><p><strong>Before (classic approach with potential overlay issues):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Dialog</span><span class="w"/><span class="n">editDialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">editDialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">(</span><span class="s">"Name"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Dialog</span><span class="w"/><span class="n">confirmDialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">confirmDialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="s">"Save changes?"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">save</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Save"</span><span class="p">,</span><span class="w"/><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">confirmDialog</span><span class="p">.</span><span class="na">open</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">editDialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">save</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">editDialog</span><span class="p">.</span><span class="na">open</span><span class="p">();</span></span></span></code></pre></div></div><p>In more complex layouts, the second dialogue may not be positioned correctly on top of the first, or focus and keyboard navigation may be lost.</p><p><strong>After (Vaadin 25, consistent overlay model):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Dialog</span><span class="w"/><span class="n">editDialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">editDialog</span><span class="p">.</span><span class="na">setModal</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Dialog</span><span class="w"/><span class="n">confirmDialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">confirmDialog</span><span class="p">.</span><span class="na">setModal</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">save</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Save"</span><span class="p">,</span><span class="w"/><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">confirmDialog</span><span class="p">.</span><span class="na">open</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">editDialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">save</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">editDialog</span><span class="p">.</span><span class="na">open</span><span class="p">();</span></span></span></code></pre></div></div><p>Unified overlay rendering reliably stacks dialogues, passes focus correctly, and restores it after closing—without the need for additional auxiliary constructs.</p><h4 id="example-2-focus-return-after-dialogue-closure">Example 2: Focus return after dialogue closure</h4><p><strong>Before (manual focus correction necessary):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">openDialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Edit"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Dialog</span><span class="w"/><span class="n">dialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dialog</span><span class="p">.</span><span class="na">addDialogCloseActionListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">openDialog</span><span class="p">.</span><span class="na">focus</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">openDialog</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">open</span><span class="p">());</span></span></span></code></pre></div></div><p>Such patterns were necessary to ensure clean keyboard navigation.</p><p><strong>After (Vaadin 25, automatic focus management):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">openDialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Edit"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Dialog</span><span class="w"/><span class="n">dialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">openDialog</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">open</span><span class="p">());</span></span></span></code></pre></div></div><p>Vaadin 25 reliably handles focus return. The code becomes shorter, more understandable and less error-prone.</p><h4 id="example-3-overlay-over-grid-and-scroll-container">Example 3: Overlay over Grid and Scroll Container</h4><p><strong>Before (unexpected positioning for scroll containers):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Grid<span class="nt">&lt;Item&gt;</span> grid = new Grid<span class="err">&lt;</span>&gt;(Item.class);</span></span><span class="line"><span class="cl">ContextMenu menu = new ContextMenu(grid);</span></span><span class="line"><span class="cl">menu.addItem("Details", e -&gt; showDetails());</span></span></code></pre></div></div><p>Depending on the layout and scrolling behaviour, the context menu could appear offset or cut off.</p><p><strong>After (Vaadin 25, more stable positioning):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Grid<span class="nt">&lt;Item&gt;</span> grid = new Grid<span class="err">&lt;</span>&gt;(Item.class);</span></span><span class="line"><span class="cl">ContextMenu menu = new ContextMenu(grid);</span></span><span class="line"><span class="cl">menu.setOpenOnClick(true);</span></span><span class="line"><span class="cl">menu.addItem("Details", e -&gt; showDetails());</span></span></code></pre></div></div><p>The new rendering model handles scroll containers and viewport changes more consistently, so overlays appear where the user expects them.</p><p>These examples show that Vaadin 25 solves many UI problems not through new APIs, but through a more robust internal architecture. For existing applications, this often means less code, fewer workarounds, and a UI that behaves stably even in borderline cases.</p><h2 id="element-api-svg-and-mathml-technical-uis-directly-from-java">Element API, SVG and MathML: Technical UIs directly from Java</h2><p>With Vaadin 25, an area is gaining importance that has so far played only a minor role in many projects: direct work with the Element API. Native support for SVG and MathML significantly expands the scope for design – especially for technical, data-driven user interfaces where classic UI components reach their limits.</p><p>Until now, such requirements have often been the point at which additional JavaScript libraries or external front-end frameworks have been introduced. Diagrams, status graphics or mathematical representations could be integrated, but led to media breaks in the code and increased integration effort. Vaadin 25 noticeably reduces this need by exposing these technologies as full-fledged elements in the server-side UI model.</p><p>The Element API enables the creation and manipulation of structured DOM elements directly from Java. With the addition of SVG, graphical primitives such as lines, circles or paths can now also be modelled on the server side. For example, it can be used to create simple diagrams, status indicators or visual markers without leaving the Vaadin component model.</p><p>A similar effect is observed with MathML. Technical applications that have to represent formulas or calculation results benefit from the fact that mathematical expressions can be described semantically correctly. The display is not only visually more precise, but also more accessible – for example, for screen readers or automated tests.</p><p>The decisive point is not so much the technical feasibility as the architectural consistency. By integrating SVG and MathML into the Element API, the Java backend retains control over UI logic, rendering, and data flow. There is no additional client-side state that needs to be synchronised or secured. Especially in safety-critical or highly regulated environments, this advantage should not be underestimated.</p><p>Of course, this approach does not replace specialised charting libraries or complex visualisation tools. Vaadin 25 deliberately positions the Element API as a tool for targeted, controlled visualisations. When it comes to clear states, technical information or explanatory graphics, this approach is often more robust and maintainable than an external dependency.</p><p>Chapter 6 thus shows another facet of Vaadin 25&rsquo;s realignment. The framework does not expand its capabilities through additional abstractions; instead, it provides direct access to established web standards. For developers, this means: more expressiveness, less integration effort and a UI that can be cleanly modelled beyond classic form and table views.</p><h3 id="before-and-after-technical-visualisation-with-and-without-element-api">Before and After: Technical Visualisation with and without Element API</h3><p>The advantages of the extended Element API are best understood through concrete examples. The following scenarios are typical of technical applications and illustrate the difference between traditional integration approaches and the Vaadin 25 solution.</p><h4 id="example-1-status-indicator-as-svg">Example 1: Status indicator as SVG</h4><p><strong>Before (external JavaScript library or client-side snippet):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Div status = new Div();</span></span><span class="line"><span class="cl">status.getElement().setProperty("innerHTML",</span></span><span class="line"><span class="cl">  "<span class="nt">&lt;svg</span><span class="na">width=</span><span class="s">'20'</span><span class="na">height=</span><span class="s">'20'</span><span class="nt">&gt;&lt;circle</span><span class="na">cx=</span><span class="s">'10'</span><span class="na">cy=</span><span class="s">'10'</span><span class="na">r=</span><span class="s">'8'</span><span class="na">fill=</span><span class="s">'green'</span><span class="nt">/&gt;&lt;/svg&gt;</span>");</span></span></code></pre></div></div><p>The SVG code is embedded here as a string. Structure, semantics, and type checking are lost, and changes are error-prone.</p><p><strong>After (Vaadin 25, SVG via Element API):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Element</span><span class="w"/><span class="n">svg</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"svg"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">svg</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"width"</span><span class="p">,</span><span class="w"/><span class="s">"20"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">svg</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"height"</span><span class="p">,</span><span class="w"/><span class="s">"20"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Element</span><span class="w"/><span class="n">circle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"circle"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">circle</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"cx"</span><span class="p">,</span><span class="w"/><span class="s">"10"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">circle</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"cy"</span><span class="p">,</span><span class="w"/><span class="s">"10"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">circle</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"r"</span><span class="p">,</span><span class="w"/><span class="s">"8"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">circle</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"fill"</span><span class="p">,</span><span class="w"/><span class="s">"green"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">svg</span><span class="p">.</span><span class="na">appendChild</span><span class="p">(</span><span class="n">circle</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">getElement</span><span class="p">().</span><span class="na">appendChild</span><span class="p">(</span><span class="n">svg</span><span class="p">);</span></span></span></code></pre></div></div><p>The SVG is now a fully modelled DOM element. Changes can be made in a targeted manner, and the structure remains comprehensible and testable.</p><h4 id="example-2-simple-bar-chart">Example 2: Simple Bar Chart</h4><p><strong>Before (passing data to client-side code):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">JsonObject</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Json</span><span class="p">.</span><span class="na">createObject</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">data</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="s">"value"</span><span class="p">,</span><span class="w"/><span class="n">75</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">ui</span><span class="p">.</span><span class="na">getPage</span><span class="p">().</span><span class="na">executeJs</span><span class="p">(</span><span class="s">"renderChart($0)"</span><span class="p">,</span><span class="w"/><span class="n">data</span><span class="p">);</span></span></span></code></pre></div></div><p>This creates additional client-side state that must be synchronised and secured.</p><p><strong>After (Vaadin 25, server-side generated SVG):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">int</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">75</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Element</span><span class="w"/><span class="n">bar</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"rect"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">bar</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"x"</span><span class="p">,</span><span class="w"/><span class="s">"0"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">bar</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"y"</span><span class="p">,</span><span class="w"/><span class="s">"0"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">bar</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"width"</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="p">.</span><span class="na">valueOf</span><span class="p">(</span><span class="n">value</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">bar</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"height"</span><span class="p">,</span><span class="w"/><span class="s">"20"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">bar</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"fill"</span><span class="p">,</span><span class="w"/><span class="s">"#4caf50"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Element</span><span class="w"/><span class="n">svg</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"svg"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">svg</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"width"</span><span class="p">,</span><span class="w"/><span class="s">"100"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">svg</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"height"</span><span class="p">,</span><span class="w"/><span class="s">"20"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">svg</span><span class="p">.</span><span class="na">appendChild</span><span class="p">(</span><span class="n">bar</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">getElement</span><span class="p">().</span><span class="na">appendChild</span><span class="p">(</span><span class="n">svg</span><span class="p">);</span></span></span></code></pre></div></div><p>The visualisation follows the server state directly. There is no duplicate logic and no hidden client-side code.</p><h4 id="example-3-representation-of-a-formula-with-mathml">Example 3: Representation of a Formula with MathML</h4><p><strong>Before (text or rendered graphic):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Span</span><span class="w"/><span class="n">formula</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="s">"E = mc^2"</span><span class="p">);</span></span></span></code></pre></div></div><p>The formula&rsquo;s semantic meaning is lost, as are its accessibility and machine-evaluability.</p><p><strong>After (Vaadin 25, MathML):</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Element</span><span class="w"/><span class="n">math</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"math"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Element</span><span class="w"/><span class="n">mrow</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"mrow"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">mrow</span><span class="p">.</span><span class="na">appendChild</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"mi"</span><span class="p">).</span><span class="na">setText</span><span class="p">(</span><span class="s">"E"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">mrow</span><span class="p">.</span><span class="na">appendChild</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"mo"</span><span class="p">).</span><span class="na">setText</span><span class="p">(</span><span class="s">"="</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">mrow</span><span class="p">.</span><span class="na">appendChild</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"mi"</span><span class="p">).</span><span class="na">setText</span><span class="p">(</span><span class="s">"m"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">mrow</span><span class="p">.</span><span class="na">appendChild</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"mo"</span><span class="p">).</span><span class="na">setText</span><span class="p">(</span><span class="s">"·"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Element</span><span class="w"/><span class="n">msup</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"msup"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">msup</span><span class="p">.</span><span class="na">appendChild</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"mi"</span><span class="p">).</span><span class="na">setText</span><span class="p">(</span><span class="s">"c"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">msup</span><span class="p">.</span><span class="na">appendChild</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Element</span><span class="p">(</span><span class="s">"mn"</span><span class="p">).</span><span class="na">setText</span><span class="p">(</span><span class="s">"2"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">mrow</span><span class="p">.</span><span class="na">appendChild</span><span class="p">(</span><span class="n">msup</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">math</span><span class="p">.</span><span class="na">appendChild</span><span class="p">(</span><span class="n">mrow</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">getElement</span><span class="p">().</span><span class="na">appendChild</span><span class="p">(</span><span class="n">math</span><span class="p">);</span></span></span></code></pre></div></div><p>The formula is now semantically correct, accessible, and clearly structured – generated directly from Java.</p><p>These examples make it clear how Vaadin 25 can be used to implement technical visualisations without additional front-end dependencies. The Element API thus becomes a targeted tool for controlled, server-side presentation – exactly where classic components are no longer sufficient.</p>
]]></content:encoded><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2026/02/ChatGPT-Image-12.-Feb.-2026-16_58_52.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2026/02/ChatGPT-Image-12.-Feb.-2026-16_58_52.jpeg"/><enclosure url="https://svenruppert.com/images/2026/02/ChatGPT-Image-12.-Feb.-2026-16_58_52.jpeg" type="image/jpeg" length="0"/></item><item><title>The Importance of UI in Import Processes</title><link>https://svenruppert.com/posts/the-importance-of-ui-in-import-processes/</link><pubDate>Mon, 09 Feb 2026 15:15:33 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/the-importance-of-ui-in-import-processes/</guid><description>Why an import needs a UI at all Import functions are often treated as purely technical details in applications. Data is read in, processed and then made available – ideally without further interaction. In practice, however, an import is rarely an invisible process. It marks a transition between existing system states, between old and new data, between trust and control. This is exactly where the need for a user interface arises.</description><content:encoded>&lt;![CDATA[<h2 id="why-an-import-needs-a-ui-at-all">Why an import needs a UI at all</h2><p>Import functions are often treated as purely technical details in applications. Data is read in, processed and then made available – ideally without further interaction. In practice, however, an import is rarely an invisible process. It marks a transition between existing system states, between old and new data, between trust and control. This is exactly where the need for a user interface arises.</p><p>In the URL shortener, the import is not designed to run in the background; it is an explicit, visible process. The UI does not take on the role of a technical decision-maker, but that of a transparent mediator. It creates a clearly demarcated space in which an import can occur without immediately modifying existing data. This decoupling alone justifies its own import interface.</p><p><strong>The current state of the source code is available on</strong></p><p><strong>GitHub:<a href="https://github.com/svenruppert/url-shortener">https://github.com/svenruppert/url-shortener</a> or<a href="https://3g3.eu/url">https://3g3.eu/url</a></strong></p><figure><img src="/images/2026/02/image-4-1024x574.png" alt="Overview of a URL shortener tool displaying a table with shortcodes, URLs, creation dates, active status, expiration, and action options." loading="lazy" decoding="async"/><ol><li><a href="https://svenruppert.com/2026/02/09/the-importance-of-ui-in-import-processes/#why-an-import-needs-a-ui-at-all">Why an import needs a UI at all</a></li><li><a href="https://svenruppert.com/2026/02/09/the-importance-of-ui-in-import-processes/#the-entry-point-in-the-application">The entry point in the application</a></li><li><a href="https://svenruppert.com/2026/02/09/the-importance-of-ui-in-import-processes/#the-import-dialog-as-a-closed-workspace">The import dialog as a closed workspace</a></li><li><a href="https://svenruppert.com/2026/02/09/the-importance-of-ui-in-import-processes/#implementation-of-the-importdialog-in-vaadin">Implementation of the ImportDialog in Vaadin</a></li><li><a href="https://svenruppert.com/2026/02/09/the-importance-of-ui-in-import-processes/#file-upload-transport-instead-of-interpretation">File upload: Transport instead of interpretation</a></li><li><a href="https://svenruppert.com/2026/02/09/the-importance-of-ui-in-import-processes/#validation-as-ui-state-change">Validation as UI state change</a></li><li><a href="https://svenruppert.com/2026/02/09/the-importance-of-ui-in-import-processes/#presentation-of-results-without-interpretation">Presentation of results without interpretation</a></li><li><a href="https://svenruppert.com/2026/02/09/the-importance-of-ui-in-import-processes/#action-control-and-apply-logic">Action Control and Apply Logic</a></li></ol><p>Importing is always associated with uncertainty. Even if the data format is known, it remains unclear how incoming datasets interact with existing datasets. Are there any overlaps? Are conflicts arising? Are individual entries incomplete or invalid? These questions cannot be answered by a technical process alone, but require visibility. The UI makes this intermediate state tangible without prematurely evaluating it.</p><p>Instead of treating the import as an atomic step, it is understood as a state in the URL shortener. The user interface displays this state, records it, and prevents it from being overlooked and inadvertently entered into a production database. This makes the import process controlled, even if the UI itself does not control the content.</p><p>The clear distribution of roles is important here. The user interface does not interpret data, validate content, or make decisions about its technical correctness. Their task is exclusively to make the current technical status of the import visible. This deliberate restraint is crucial, as it prevents UI and import logic from being conflated.</p><p>So the import doesn&rsquo;t need a UI because it&rsquo;s complex, but because it&rsquo;s a transition. The surface acts as a boundary between file and system, between potential changes and existing persistence. It gives the import a place, a time and a clear context – and that&rsquo;s exactly where its raison d&rsquo;être lies.</p><h2 id="the-entry-point-in-the-application">The entry point in the application</h2><p>The import does not start automatically in the URL shortener; it only starts via a deliberately designed entry point in the application. It is where users manage existing short URLs and view the current database. This clearly positions the import as an exceptional act rather than part of the everyday processing flow.</p><p><figure><img src="/images/2026/02/image-3-1024x578.png" alt="Screenshot of a URL shortener interface showing the import section, including options to upload a ZIP file, preview of URLs, and displayed conflicts." loading="lazy" decoding="async"/><figure><img src="/images/2026/02/image-2-1024x702.png" alt="User interface for importing a ZIP file with a preview section showing a staging ID, number of new items, conflicts, and invalid entries. The interface includes a table for displaying short codes, URLs, and activation statuses." loading="lazy" decoding="async"/></p><p>Entry is done via an explicit action that opens a modal dialogue. This action only creates a new instance of the import dialogue and gives it the necessary dependencies. Further logic is deliberately not provided at this point. By opening the dialogue, the user leaves the application&rsquo;s normal working context and enters a clearly demarcated area where the import is fully encapsulated.</p><p>The modal character of the dialogue fulfils an important function here. It indicates that the import is not a background process running in parallel, but a coherent process with its own state. As long as the dialogue is open, the rest of the UI state remains unchanged. There is no implicit update and no automatic data transfer.</p><p>From the user guidance perspective, this creates a clear-cut. The import is not understood as an extension of the table view, but as a temporary workspace. This separation prevents import actions from being mixed with regular editing steps. At the same time, it creates a mental orientation: everything that happens within this dialogue belongs to the import, and nothing beyond it.</p><p>From a technical standpoint, the UI assumes no further responsibility at this point. The entry point does not initiate validation or server communication; instead, it delegates the entire import process to the dialogue itself. Only when the user consciously uploads a file does the import process begin.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">importButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Import"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">importButton</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ImportDialog</span><span class="w"/><span class="n">dialog</span><span class="w"/><span class="o">=</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ImportDialog</span><span class="p">(</span><span class="n">urlShortenerClient</span><span class="p">,</span><span class="w"/><span class="p">()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refreshGrid</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">open</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>An excerpt from the corresponding view illustrates this principle. The button opens the import dialogue and optionally passes a callback, allowing a return to the normal UI context after a successful import.</p><p>The code makes it clear that the entry point merely instantiates and opens the dialogue. There is no pre-check or interaction with the server or import APIs. The callback is used only for downstream view updates and is not part of the import process itself.</p><p>Thus, the entry point defines not only where the import starts, but also how it is perceived. It is a clear turning point in the context of use and forms the basis for all subsequent steps in the import dialogue.</p><h2 id="the-import-dialog-as-a-closed-workspace">The import dialog as a closed workspace</h2><p>When the import dialogue opens, the application deliberately switches to a well-defined working mode. The dialogue is not intended as a mere overlay, but as a standalone UI space with its own logic, state, and clearly defined boundaries. Everything that happens within this dialogue belongs exclusively to the import and has no effect on the existing database until the process is explicitly completed.</p><p>This partitioning is a central design element. While the dialogue is open, the rest of the application remains frozen in its previous state. Tables, filters or active selections are neither updated nor influenced. This creates a clear separation between ongoing work and the import process, providing both technical and mental orientation.</p><p>The import dialogue is implemented as a standalone Vaadin component. It is fully initialised when opened and has no implicit return channel to the calling view. This decoupling allows the dialog to manage its internal state independently. Neither when opened nor during the interaction is it assumed that import data already exists. The initial state is always neutral and empty.</p><p>Structurally, the dialogue is divided into distinct sections, each representing a specific phase of import. However, these areas are not implemented as step-by-step wizards; instead, they coexist within a common framework. This keeps the dialogue fully visible at all times and avoids implicit transitions that could obscure the actual state.</p><p>It is particularly important that the dialogue does not make a technical interpretation of the import data. It simply provides UI surfaces where results are displayed as soon as the server delivers them. Whether these spaces remain empty or are filled depends solely on the current import state, not on assumptions or expectations of the UI.</p><p>The control elements of the dialogue also follow this principle. Actions such as validation or application of the import are not permanently available, but are tied to the internal state of the dialogue. As long as there is no corresponding import data, the dialogue remains inactive. The UI does not enforce an order or progression, but only reacts to clearly defined state changes.</p><p>The import dialogue thus acts as a controlled container for a potentially critical operation. It fully encapsulates the import, makes its current status visible, and prevents incomplete or unchecked data from taking effect unnoticed. This clear boundary underpins all subsequent steps in the import process and explains why the dialogue was deliberately designed as a closed workspace.</p><h2 id="implementation-of-the-importdialog-in-vaadin">Implementation of the ImportDialog in Vaadin</h2><p>The import dialogue in the URL shortener clearly shows how little &ldquo;framework magic&rdquo; it takes to build a complete, state-driven UI with Vaadin Flow. The entire functionality is built from a few easy-to-understand building blocks: a dialogue container, standard components such as<code>upload</code>,<code>grid</code>,<code>tabs,</code> and<code>button</code>, and clear state variables that carry the dialogue throughout the import process. The result is an interface that treats the import as a separate workspace while keeping the source code manageable.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">ImportDialog</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">dialog</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">URLShortenerClient</span><span class="w"/><span class="n">client</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Upload</span><span class="w"/><span class="n">upload</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Upload</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">zipBytes</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">btnValidate</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Validate"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">btnApply</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Apply Import"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">btnClose</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Close"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Div</span><span class="w"/><span class="n">applyHint</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">string</span><span class="w"/><span class="n">stagingId</span><span class="p">;</span></span></span></code></pre></div></div><p>Getting started is already remarkably simple. Dialog is a normal Java class that extends<code>Dialogue</code>. This means the UI is not a declarative construct or a template; it can be fully traced in Java code. At the same time, the dialogue deliberately remains stateful: it holds both the uploaded ZIP bytes and the<code>stagingId</code> generated by server-side validation.</p><p>With this structure, it&rsquo;s already clear how Vaadin Flow works here: Components are objects, and the dialogue has them like ordinary fields. The UI is thus automatically addressable without requiring &ldquo;UI IDs&rdquo; or bindings. At the same time, the import state is not kept externally; it belongs precisely to the dialogue in which it is made visible.</p><p>The constructor then shows the core of the Vaadin mechanics. With just a few lines, the dialogue is configured as a modal, resizable container. There is no separate configuration file and no DSL, but pure Java calls. This allows the import dialogue&rsquo;s visual framework to be understood directly.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">setHeaderTitle</span><span class="p">(</span><span class="s">"Import"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">setModal</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">setResizable</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">setDraggable</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">setWidth</span><span class="p">(</span><span class="s">"1100px"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">setHeight</span><span class="p">(</span><span class="s">"750px"</span><span class="p">);</span></span></span></code></pre></div></div><p>Immediately afterwards, it becomes apparent how Vaadin Flow typically models interactions. The buttons are initially deactivated and are unlocked only via events. This initialisation is a central part of the state model. The dialogue always starts neutrally and does not force progress unless there is a ZIP file.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">btnValidate</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnValidate</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnApply</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_SUCCESS</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnApply</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnClose</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">close</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnValidate</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">validate</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnApply</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">applyImport</span><span class="p">());</span></span></span></code></pre></div></div><p>The interaction of UI and file upload can also be read directly. The<code>upload</code> will be limited to ZIP files and will have a maximum size. The key point here is the in-memory handler: once the file is successfully uploaded, the bytes are placed in<code>zipBytes</code> and the next step is unlocked by enabling<code>btnValidate</code>. The transition from &ldquo;upload available&rdquo; to &ldquo;validation possible&rdquo; is thus a single, very understandable state change.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">upload</span><span class="p">.</span><span class="na">setAcceptedFileTypes</span><span class="p">(</span><span class="s">".zip"</span><span class="p">,</span><span class="w"/><span class="n">APPLICATION_ZIP</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">upload</span><span class="p">.</span><span class="na">setMaxFiles</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">upload</span><span class="p">.</span><span class="na">setMaxFileSize</span><span class="p">(</span><span class="n">IMPORT_MAX_ZIP_BYTES</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">UploadHandler</span><span class="w"/><span class="n">inMemoryUploadHandler</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UploadHandler</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">inMemory</span><span class="p">((</span><span class="n">metadata</span><span class="p">,</span><span class="w"/><span class="n">bytes</span><span class="p">)</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">metadata</span><span class="p">.</span><span class="na">fileName</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">contentLength</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">metadata</span><span class="p">.</span><span class="na">contentLength</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"uploaded file: fileName: {} , contentLength {}"</span><span class="p">,</span><span class="w"/><span class="n">fileName</span><span class="p">,</span><span class="w"/><span class="n">contentLength</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">zipBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">bytes</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"setting zipBytes.."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">btnValidate</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">upload</span><span class="p">.</span><span class="na">setUploadHandler</span><span class="p">(</span><span class="n">inMemoryUploadHandler</span><span class="p">);</span></span></span></code></pre></div></div><p>The actual UI structure is then created in<code>buildContent().</code> Here, too, the strength of Vaadin Flow becomes apparent: Layouts are components, and the dialogue is simply built by assembling components. Headings, upload area, preview summary, tabs, and a central content container are merged into a single VerticalLayout. The code reads like a description of the interface.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"/><span class="n">root</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">VerticalLayout</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">H3</span><span class="p">(</span><span class="s">"Upload ZIP"</span><span class="p">),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">upload</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">H3</span><span class="p">(</span><span class="s">"Preview"</span><span class="p">),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">summary</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">applyRow</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tabs</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tabContent</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">root</span><span class="p">.</span><span class="na">setSizeFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">root</span><span class="p">.</span><span class="na">setPadding</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">renderTab</span><span class="p">(</span><span class="n">tabContent</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">return</span><span class="w"/><span class="n">root</span><span class="p">;</span></span></span></code></pre></div></div><p>For displaying results, the dialogue uses two<code>grid</code> instances and switches between them via tabs. The decisive factor is that the tab interaction does not trigger any backend logic; it only updates the visible projection. In Vaadin Flow, switching is a single listener that empties the container and uses the appropriate combination of paging bar and grid.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">tabs</span><span class="p">.</span><span class="na">addSelectedChangeListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">renderTab</span><span class="p">(</span><span class="n">tabContent</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">renderTab</span><span class="p">(</span><span class="n">VerticalLayout</span><span class="w"/><span class="n">container</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">container</span><span class="p">.</span><span class="na">removeAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">container</span><span class="p">.</span><span class="na">setSizeFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">tabs</span><span class="p">.</span><span class="na">getSelectedTab</span><span class="p">()</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">tabInvalid</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">container</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">pagingInvalid</span><span class="p">,</span><span class="w"/><span class="n">gridInvalid</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">container</span><span class="p">.</span><span class="na">expand</span><span class="p">(</span><span class="n">gridInvalid</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">container</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">pagingConflicts</span><span class="p">,</span><span class="w"/><span class="n">gridConflicts</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">container</span><span class="p">.</span><span class="na">expand</span><span class="p">(</span><span class="n">gridConflicts</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>At this point, it becomes clear why the implementation remains so compact despite several states. Vaadin Flow provides the UI building blocks, and the dialogue connects them via a few, clearly defined state variables and listeners. The actual import process remains server-side. The UI only needs to know when a state is reached and how to display it.</p><p>This makes the<code>ImportDialog</code> a good example of how Vaadin Flow reduces complexity: not by hiding logic, but by using a direct programming model in which UI components, events, and state converge into a single, easy-to-read Java codebase. Especially for a minimalist import function, a complete, robust interface can be created quickly without requiring additional framework layers.</p><h2 id="file-upload-transport-instead-of-interpretation">File upload: Transport instead of interpretation</h2><p>In the import dialogue, the actual import process does not begin with a technical decision, but with a purely technical step: uploading a ZIP file. This moment marks the transition from an empty, neutral dialogue state to a potentially processable import without already making any content evaluation.</p><p>The upload component is firmly anchored in the dialogue and visible from the beginning. It deliberately serves as the first point of interaction within the closed work area. However, their task is clearly limited. The upload is used exclusively to receive a file and make its content temporarily available in the UI context. Neither the structure nor the semantics of the data contained play a role at this point.</p><p>This attitude is directly reflected in the source code. The upload is configured to accept exactly one ZIP file, and the maximum file size must not be exceeded. These restrictions do not serve the purpose of technical validation; they serve only the technical validation of the UI process.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">upload</span><span class="p">.</span><span class="na">setAcceptedFileTypes</span><span class="p">(</span><span class="s">".zip"</span><span class="p">,</span><span class="w"/><span class="n">APPLICATION_ZIP</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">upload</span><span class="p">.</span><span class="na">setMaxFiles</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">upload</span><span class="p">.</span><span class="na">setMaxFileSize</span><span class="p">(</span><span class="n">IMPORT_MAX_ZIP_BYTES</span><span class="p">);</span></span></span></code></pre></div></div><p>Once a file is selected and uploaded, an in-memory upload handler processes it. The dialogue saves the complete contents of the ZIP file as a byte array in an internal field. At this point, there is no check to verify whether the file contains import data or is structured correctly. In the UI, the file is initially a binary block.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">UploadHandler</span><span class="w"/><span class="n">inMemoryUploadHandler</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UploadHandler</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">inMemory</span><span class="p">((</span><span class="n">metadata</span><span class="p">,</span><span class="w"/><span class="n">bytes</span><span class="p">)</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">zipBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">bytes</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">btnValidate</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">upload</span><span class="p">.</span><span class="na">setUploadHandler</span><span class="p">(</span><span class="n">inMemoryUploadHandler</span><span class="p">);</span></span></span></code></pre></div></div><p>These few lines mark a crucial UI state change. Upon successful upload completion, the validation button is enabled. That&rsquo;s all that happens at this point. The upload alone does not trigger any server communication and does not create an import state. It merely signals that there is now enough information to trigger the next user step.</p><p>Errors during uploads are also dealt with exclusively from a technical perspective. If a file is rejected, for example, due to an incorrect data type or a file size that is too large, the UI responds with a corresponding notification. However, there is still no evaluation of the file&rsquo;s content.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">upload</span><span class="p">.</span><span class="na">addFileRejectedListener</span><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">errorMessage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">event</span><span class="p">.</span><span class="na">getErrorMessage</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="w"/><span class="n">notification</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="n">errorMessage</span><span class="p">,</span><span class="w"/><span class="n">5000</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">Position</span><span class="p">.</span><span class="na">MIDDLE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">notification</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">NotificationVariant</span><span class="p">.</span><span class="na">LUMO_ERROR</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>The upload section of the dialogue thus clarifies a central design principle: The user interface does not interpret imported data. It merely ensures that a file is received in a technically correct manner and transfers this state into a clearly recognisable next possibility for action.</p><p>Only by clicking the &ldquo;Validate&rdquo; button does the dialogue leave this purely technical state of preparation. The file upload thus serves as the necessary, but deliberately content-free, basis for all subsequent steps of the import process.</p><h2 id="validation-as-ui-state-change">Validation as UI state change</h2><p>By clicking on the &ldquo;Validate&rdquo; button, the import dialogue leaves its purely preparatory state for the first time. Up to this point, only technical requirements were created: a file was uploaded and stored in memory without its content being interpreted or further processed. The<code>validate()</code> method now marks the clear transition from a passive UI state to a state-changing step that makes the import visible for the first time.</p><p>From a user interface perspective, validation is not a technical review process, but a coordinated flow of multiple UI updates, all triggered by a single user action. The dialogue deliberately assumes no responsibility for the content. It asks the server to verify the uploaded ZIP content and processes only the technical response it receives.</p><p>The introduction to the method is correspondingly defensive. First, the dialogue state checks whether a ZIP file exists. If this is missing, the UI displays a brief notification and cancels the process. At this point, no exception is propagated, and no internal state is changed. The validation is thus clearly tied to a previous, explicit user action.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">zipBytes</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">zipBytes</span><span class="p">.</span><span class="na">length</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"No ZIP uploaded."</span><span class="p">,</span><span class="w"/><span class="n">2500</span><span class="p">,</span><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">Position</span><span class="p">.</span><span class="na">TOP_CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Only when this prerequisite is fulfilled is the actual validation step triggered. The dialogue passes the complete contents of the ZIP file to the client and calls the dedicated server endpoint for import preview. For the UI, this call is a black box. It does not know the internal validation rules or the criteria by which entries are classified as new, conflicting, or invalid.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"/><span class="n">previewJson</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">client</span><span class="p">.</span><span class="na">importValidateRaw</span><span class="p">(</span><span class="n">zipBytes</span><span class="p">);</span></span></span></code></pre></div></div><p>The server&rsquo;s response is received in its entirety as a JSON string. Instead of converting this into a fixed data model, the UI extracts the individual values relevant to representing the import state. This includes the generated<code>stagingId,</code> as well as the counts of new, conflicting, and invalid entries. These values are immediately visible in the interface and constitute the first concrete import state displayed by the dialogue.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">this</span><span class="p">.</span><span class="na">stagingId</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">extractJsonString</span><span class="p">(</span><span class="n">previewJson</span><span class="p">,</span><span class="w"/><span class="s">"stagingId"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kt">int</span><span class="w"/><span class="n">newItems</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">extractJsonInt</span><span class="p">(</span><span class="n">previewJson</span><span class="p">,</span><span class="w"/><span class="s">"newItems"</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kt">int</span><span class="w"/><span class="n">conflicts</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">extractJsonInt</span><span class="p">(</span><span class="n">previewJson</span><span class="p">,</span><span class="w"/><span class="s">"conflicts"</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kt">int</span><span class="w"/><span class="n">invalid</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">extractJsonInt</span><span class="p">(</span><span class="n">previewJson</span><span class="p">,</span><span class="w"/><span class="s">"invalid"</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">);</span></span></span></code></pre></div></div><p>With these values set, the character of the dialogue changes noticeably. The previously empty preview area is populated, and the import receives an identity for the first time: the<code>stagingId</code>. Nevertheless, the UI remains strictly descriptive. It does not evaluate whether the figures are plausible or in a certain relationship to each other. It only shows the current technical status.</p><p>At the same time, the dialogue reads the result lists directly from the server response. Iterators are used to read JSON arrays for conflicts and invalid entries, and to convert them into UI-internal row objects. These are then assigned to the corresponding grids. Here, too, there is no interpretation. If an array is empty or non-existent, the tables remain empty – a state that the UI treats as completely valid.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">obj</span><span class="w"/><span class="p">:</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ItemsArrayIterator</span><span class="p">(</span><span class="n">r</span><span class="p">,</span><span class="w"/><span class="s">"conflictItems"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">m</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">JsonUtils</span><span class="p">.</span><span class="na">parseJson</span><span class="p">(</span><span class="n">obj</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">conflictRows</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">ConflictRow</span><span class="p">.</span><span class="na">from</span><span class="p">(</span><span class="n">m</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>After the grids are filled, paging information is updated, and the associated UI components are synchronised. This step is also performed exclusively based on the previously determined result lists, without any additional feedback to the server.</p><p>With the completion of the<code>validate()</code> method, the import dialogue is in a new, stable state. The uploaded data is validated on the server side; the results are visible, and the dialogue can now decide which further actions to offer the user. Note that validation itself does not trigger an import. It only changes the UI state and creates transparency about what a subsequent import would do.</p><p>The<code>validate()</code> method is thus the central linchpin of the entire import dialogue. It combines the upload and presentation of results without making technical decisions itself. In this role, it becomes the core of the UI-controlled import process.</p><h2 id="presentation-of-results-without-interpretation">Presentation of results without interpretation</h2><p>After validation, the import dialogue is in a state where concrete results are available for the first time. However, these results are not interpreted, weighted, or further processed; they are only presented. The last chapter describes exactly this moment: the transformation of raw information delivered on the server side into visible UI structures – without the user interface drawing its own conclusions.</p><p>Central to this chapter is the realisation that the dialogue lacks a technical view of conflicts or invalid entries. He knows neither their significance nor their effects. Instead, it performs a purely mechanical task: it takes lists of results and renders them into prepared UI components.</p><p>This projection is done via two separate tables, each associated with a specific result type. Conflicts and invalid entries are deliberately not presented together; they are kept in separate contexts. The dialogue offers two tabs for this purpose, which the user can switch between. However, this change only changes the view of the data, not its content or state.</p><p>The technical basis of this representation consists of two grid components, which are already fully configured when the dialogue is set up. Columns, widths, and display logic are fixed before it is even known whether data will ever be displayed. The grids thus exist independently of the existence of results and represent a stable projection surface.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">gridConflicts</span><span class="p">.</span><span class="na">addColumn</span><span class="p">(</span><span class="n">ConflictRow</span><span class="p">::</span><span class="n">shortCode</span><span class="p">).</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"shortCode"</span><span class="p">).</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">setFlexGrow</span><span class="p">(</span><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">gridConflicts</span><span class="p">.</span><span class="na">addColumn</span><span class="p">(</span><span class="n">ConflictRow</span><span class="p">::</span><span class="n">d</span><span class="w"/><span class="n">iff</span><span class="p">).</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"diff"</span><span class="p">).</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">setFlexGrow</span><span class="p">(</span><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">gridConflicts</span><span class="p">.</span><span class="na">addColumn</span><span class="p">(</span><span class="n">ConflictRow</span><span class="p">::</span><span class="n">existingUrl</span><span class="p">).</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"existingUrl"</span><span class="p">).</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">gridConflicts</span><span class="p">.</span><span class="na">addColumn</span><span class="p">(</span><span class="n">ConflictRow</span><span class="p">::</span><span class="n">incomingUrl</span><span class="p">).</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"incomingUrl"</span><span class="p">).</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span></span></span></code></pre></div></div><p>These grids are filled not through a traditional data model, but via direct iteration over JSON fragments in the server response. The UI reads each object from its corresponding array and converts it into a simple line representation. These row objects contain exactly the fields that should be displayed, no more and no less.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">obj</span><span class="w"/><span class="p">:</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ItemsArrayIterator</span><span class="p">(</span><span class="n">r</span><span class="p">,</span><span class="w"/><span class="s">"conflictItems"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">m</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">JsonUtils</span><span class="p">.</span><span class="na">parseJson</span><span class="p">(</span><span class="n">obj</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">conflictRows</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">ConflictRow</span><span class="p">.</span><span class="na">from</span><span class="p">(</span><span class="n">m</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>It is noteworthy that the dialogue makes no assumptions about the number of expected entries or about which fields must be present. If an array is empty or cannot be read, the associated table remains empty. This state is not treated by the UI as an error; it is considered a valid result of validation.</p><p>The tab control also follows this principle of neutrality. The currently selected tab only determines which table is visible. When switching, no data is reloaded, and no states are changed. The UI only shows a different section of the same import state.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">tabs</span><span class="p">.</span><span class="na">addSelectedChangeListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">renderTab</span><span class="p">(</span><span class="n">tabContent</span><span class="p">));</span></span></span></code></pre></div></div><p>The presentation of results is supplemented by a simple paging component that operates only on results already loaded. It provides a better overview of large datasets but is fully local and does not execute any additional server queries. Here, too, no content filter is applied; paging is purely representation-oriented.</p><p>The interplay of grids, tabs, and paging results in a deliberately restrained user interface. It shows what&rsquo;s there and doesn&rsquo;t show anything that hasn&rsquo;t been clearly delivered. Neither is there an attempt to compensate for missing data, nor are implicit assumptions made about the &ldquo;actual&rdquo; state of the import.</p><p>This makes it clear that the result displayed in the import dialogue is not an analysis tool, but a mirror. It reflects the state delivered by the server exactly and defers any further evaluation to subsequent steps in the import process.</p><h2 id="action-control-and-apply-logic">Action Control and Apply Logic</h2><p>After uploading, validating, and displaying the results, one central question remains in the import dialogue: Can the import actually be used? The answer to this question is not implicit or automatic. Instead, it is completely controlled by the UI state.</p><p>The basis of this control is not an additional server call, but the already known import state. All information relevant to the decision is already available to the UI. The<code>updateApplyState()</code> method serves as a central hub that interprets the state and translates it into concrete UI activations or deactivations.</p><p>The starting point is a strictly defensive default state. Regardless of what happened before, the Apply button will be disabled first. The UI never assumes that an import is automatically applicable. Only when all conditions are explicitly fulfilled is this state lifted.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">btnApply</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnApply</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">"Apply Import"</span><span class="p">);</span></span></span></code></pre></div></div><p>The first hard test point is the presence of a<code>stagingId</code>. Without this identifier, there is no valid import context from a UI perspective. Even if the data is already displayed, the import remains inapplicable until a server-side confirmed staging state is reached. The UI does not treat this case as an error, but as an incomplete state.</p><p>Then, the two result dimensions that have already been made visible in the previous chapters are considered: invalid entries and conflicts. Invalid entries generally block the import. As soon as at least one invalid record exists, the Apply function remains deactivated, and the dialogue explicitly communicates this state via a hint text. The UI does not force a correction; it simply makes it clear that an import is not possible under these conditions.</p><p>Conflicts, on the other hand, are treated differently. From a UI perspective, they do not represent an absolute exclusion but rather a deliberate decision-making process. The dialogue includes a checkbox for this purpose, allowing the user to specify whether conflicting entries should be skipped during import. Only with this explicit consent is the import released despite existing conflicts.</p><p>This differentiation is directly evident in the interplay of the checkbox, information text and apply button. Activating or deactivating the checkbox immediately triggers a reassessment of the UI state without loading or recalculating any additional data. The UI responds only to the known state.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">conflicts</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">0</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">chkSkipConflicts</span><span class="p">.</span><span class="na">getValue</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">applyHint</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">"Apply disabled: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">conflicts</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" conflict(s). Tick "</span><span class="n">Skip</span><span class="w"/><span class="n">conflicts</span><span class="w"/><span class="n">on</span><span class="w"/><span class="n">apply</span><span class="s">" to proceed."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>When the import is finally approved, not only does the button&rsquo;s activation change, but its label does as well. This allows the UI to clearly signal the conditions under which the import will be executed. This visual notice is part of the action management and serves to ensure transparency, not to enforce professional rules.</p><p>By clicking &ldquo;Apply Import&rdquo;, the dialogue leaves its purely display and decision modes. Only at this point is another server call triggered, which actually applies the previously validated import. Up to this point, the UI has only managed states, displayed them and demanded decisions.</p><p>The action control thus forms the deliberate conclusion of the import dialogue. It bundles all previously built-up information and converts it into an explicit user decision. It is precisely this restraint – not to apply anything automatically, to imply anything – that makes dialogue a controlled and comprehensible tool within the application.</p>
]]></content:encoded><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2026/02/ChatGPT-Image-9.-Feb.-2026-15_08_11.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2026/02/ChatGPT-Image-9.-Feb.-2026-15_08_11.jpeg"/><enclosure url="https://svenruppert.com/images/2026/02/ChatGPT-Image-9.-Feb.-2026-15_08_11.jpeg" type="image/jpeg" length="0"/></item><item><title>JSON export in Vaadin Flow</title><link>https://svenruppert.com/posts/20224/</link><pubDate>Thu, 05 Feb 2026 16:50:15 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/20224/</guid><description>Export functions are often seen as a purely technical side task: one button, one download, done. In a Vaadin-based application, however, it quickly becomes apparent that exporting is much more than writing data to a file. It is a direct extension of the UI state, an infrastructural contract between frontend and backend, and a decisive factor for maintainability and predictability.</description><content:encoded>&lt;![CDATA[<p>Export functions are often seen as a purely technical side task: one button, one download, done. In a Vaadin-based application, however, it quickly becomes apparent that exporting is much more than writing data to a file. It is a direct extension of the UI state, an infrastructural contract between frontend and backend, and a decisive factor for maintainability and predictability.</p><ol><li><a href="https://svenruppert.com/2026/02/05/20224/#export-from-a-ui-point-of-view-more-than-a-download-button">Export from a UI point of view: more than a download button</a></li><li><a href="https://svenruppert.com/2026/02/05/20224/#initial-situation-functional-export-but-non-ui">Initial situation: functional export, but non-UI</a></li><li><a href="https://svenruppert.com/2026/02/05/20224/#design-goal-export-as-a-deterministic-ui-workflow">Design Goal: Export as a Deterministic UI Workflow</a></li><li><a href="https://svenruppert.com/2026/02/05/20224/#uniform-responses-as-a-prerequisite-for-clean-ui-logic">Uniform responses as a prerequisite for clean UI logic</a></li><li><a href="https://svenruppert.com/2026/02/05/20224/#filter-logic-as-a-common-language-between-the-grid-and-export">Filter logic as a common language between the grid and export</a></li><li><a href="https://svenruppert.com/2026/02/05/20224/#download-mechanics-in-vaadin-button-download">Download mechanics in Vaadin: Button ≠ Download</a></li><li><a href="https://svenruppert.com/2026/02/05/20224/#streamresource-export-on-demand-instead-of-in-advance">StreamResource: Export on demand instead of in advance</a></li><li><a href="https://svenruppert.com/2026/02/05/20224/#paging-boundaries-as-a-protective-mechanism">Paging boundaries as a protective mechanism</a><ol><li><a href="https://svenruppert.com/2026/02/05/20224/#real-json-export-from-the-running-system">Real JSON export from the running system</a></li></ol></li><li><a href="https://svenruppert.com/2026/02/05/20224/#effects-on-maintainability-and-comprehensibility">Effects on maintainability and comprehensibility</a></li><li><a href="https://svenruppert.com/2026/02/05/20224/#conclusion">Conclusion</a></li></ol><p>This article shows how a JSON-based export was deliberately designed as a UI-driven workflow in the URL shortener project. The focus is not on file formats or complex backend abstractions, but on the clean embedding of the export in a Vaadin Flow interface: filter coupling, download mechanics, paging boundaries and clear responsibilities between UI, client and server.</p><p>**The current source code can be found on GitHub under<a href="https://github.com/svenruppert/url-shortener">https://github.com/svenruppert/url-shortener</a> or<a href="https://3g3.eu/url">https://3g3.eu/url</a></p><figure><img src="/images/2026/02/image-1.png" alt="Overview page of a URL shortener tool displaying a list of shortened URLs, their user IDs, creation dates, status indicators, and options for managing the links." loading="lazy" decoding="async"/><h2 id="export-from-a-ui-point-of-view-more-than-a-download-button">Export from a UI point of view: more than a download button</h2><p>In classic web applications, export is often thought of as an isolated API endpoint. From the perspective of a Vaadin UI, this consideration falls short. For the user, export is not a technical process but a consequence of the current UI state: the filters applied, the sorting, and the paging limits.</p><p>An export that ignores this coupling immediately leads to cognitive breaks. The display in the grid shows a certain amount of data, but the export provides something else – be it more, less or simply different data. This is exactly where it is determined whether an application is perceived as consistent.</p><p>The claim in the project was therefore clear: The export is not a special function, but a mirror of the UI state.</p><p>This decision shapes all further design steps – from the filter generation to the download mechanics to the structure of the export data itself.</p><h2 id="initial-situation-functional-export-but-non-ui">Initial situation: functional export, but non-UI</h2><p>Before the revision, an export was already technically possible. Data could be read on the server and returned to the client as JSON, ensuring the basic functionality was fulfilled. However, from a user interface perspective, this implementation introduced several structural issues that only became apparent upon closer inspection.</p><p>The response structures were inconsistent and required the client to interpret them in context. The meaning and interaction of HTTP status codes and response bodies were not explicitly defined; they were derived from implicit assumptions in the client code. At the same time, there was no clear connection between the export and the currently visible filters in the interface. From the UI&rsquo;s perspective, it was only possible to trace the data that was actually exported indirectly. In addition, there was special logic for empty results or error cases that could not be derived consistently from the response itself but were distributed across several places in the client.</p><p>From Vaadin&rsquo;s perspective, the export was not an integrated UI workflow but rather an isolated technical endpoint. The UI had to provide knowledge of special cases, status codes, and response formats that were not explicitly covered by a contractually defined framework. This state of affairs was also reflected in the test landscape: tests were often based on concrete string representations or complete JSON output rather than on clearly defined functional structures. Changes to filters, sorting or response formats therefore had to be followed up in several places and carried an increased risk of unintended side effects.</p><p>In short, the export worked technically but did not meet the requirements for a UI-enabled, traceable, and maintainable component in a Vaadin application.</p><h2 id="design-goal-export-as-a-deterministic-ui-workflow">Design Goal: Export as a Deterministic UI Workflow</h2><p>The central goal of the redesign was not &ldquo;more features&rdquo;, but predictability. For the Vaadin UI, this means:</p><p>The export uses the same filters as the grid and thus reflects the current UI state. Paging limits are deliberately set and comprehensible for developers and users alike, so that the scope and character of the export remain clearly recognisable. Success, empty results and error cases are clearly distinguishable and can be handled in the UI without special logic. At the same time, the download behaves browser-compliantly and UI-stably without affecting the current UI state.</p><p>From the UI&rsquo;s perspective, an export must not have its own state. He must not &ldquo;think&rdquo; anything, expand anything, or change anything implicitly. It is a snapshot of what the user sees – nothing more, nothing less.</p><h2 id="uniform-responses-as-a-prerequisite-for-clean-ui-logic">Uniform responses as a prerequisite for clean UI logic</h2><p>In a Vaadin application, API responses have an immediate effect on the UI code, as each response is typically translated directly into UI states, component logic, and user feedback. In contrast to purely client-side frontends, the UI logic is tightly coupled to server-side processing: Each response directly updates component state, enables or disables controls, and renders feedback to the user.</p><p>In this context, inconsistent response formats inevitably lead to complex if-else cascades in the UI code. Special treatments for seemingly trivial cases, such as &ldquo;empty&rdquo; exports or different error states, must be explicitly requested. The UI code starts by interpreting technical details of the API – such as certain HTTP status codes or the presence of individual JSON fields – instead of relying on clearly defined business signals. This not only increases the code complexity but also complicates the interface behaviour during extensions, making it harder to understand and more error-prone.</p><p>In the URL shortener project, this problem was solved by introducing an explicit and stable response structure. Regardless of whether an export record is empty or contains an error, the response always follows the same structure. HTTP status codes are still used to signal the rough outcome of a request, but they do not serve as the sole signifier. The actual technical information – such as the context, scope, and content of the export – is transmitted in full and consistently in JSON format.</p><p>A simplified, real export from the system illustrates this approach:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">json</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span></span></span><span class="line"><span class="cl"> <span class="nt">"formatVersion"</span><span class="p">:</span><span class="s2">"1"</span><span class="p">,</span></span></span><span class="line"><span class="cl"> <span class="nt">"mode"</span><span class="p">:</span><span class="s2">"filtered"</span><span class="p">,</span></span></span><span class="line"><span class="cl"> <span class="nt">"exportedAt"</span><span class="p">:</span><span class="s2">"2026-02-05T11:28:54.582886239Z"</span><span class="p">,</span></span></span><span class="line"><span class="cl"> <span class="nt">"total"</span><span class="p">:</span><span class="mi">9</span><span class="p">,</span></span></span><span class="line"><span class="cl"> <span class="nt">"items"</span><span class="p">:</span><span class="p">[</span><span class="err">/*</span><span class="err">subject</span><span class="err">records</span><span class="err">*/</span><span class="p">]</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This creates a clear and stable contract for the Vaadin UI. The UI code can rely on the fact that metadata such as mode, exportedAt, or total is always present and interpreted consistently. The interface no longer has to guess whether an export was successful or whether there are special cases. Instead, the process can be designed in a linear, deterministic way: metadata is evaluated, the scope is checked, and the user data is processed or reported back to the user.</p><p>This structure has far-reaching consequences for UI logic. Loading indicators, confirmation dialogs or error messages can be derived directly from the structured response, without additional special logic or context-dependent checks. This keeps the interface clear, predictable, and closely linked to the technical significance of the answer, rather than tied to technical special cases or implicit assumptions.</p><h2 id="filter-logic-as-a-common-language-between-the-grid-and-export">Filter logic as a common language between the grid and export</h2><p>A crucial Vaadin-specific point is the reuse of the filter logic. There is no separate export filter in the project. Instead, the export is generated exclusively from the current UI state.</p><p>The SearchBar acts as the only source of truth:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">UrlMappingListRequest</span><span class="w"/><span class="nf">buildFilter</span><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">page</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">size</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">UrlMappingListRequest</span><span class="w"/><span class="n">req</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">UrlMappingListRequest</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="na">setPage</span><span class="p">(</span><span class="n">page</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="na">setSize</span><span class="p">(</span><span class="n">size</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="na">setActiveState</span><span class="p">(</span><span class="n">activeState</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="na">setCodePart</span><span class="p">(</span><span class="n">codeField</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="na">setUrlPart</span><span class="p">(</span><span class="n">urlField</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="na">setFrom</span><span class="p">(</span><span class="n">from</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="na">setTo</span><span class="p">(</span><span class="n">to</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="na">setSort</span><span class="p">(</span><span class="n">sort</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">req</span><span class="p">.</span><span class="na">setDir</span><span class="p">(</span><span class="n">dir</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"/><span class="n">req</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This Request object is used for both grid display and export. This guarantees:</p><p>Display and export thus produce identical results, since both are based on the same filter definitions. Changes to filters or collations automatically and consistently affect display and export without requiring additional code. At the same time, there are no hidden or implicit export parameters, so the export behaviour can be fully explained by the UI state.</p><p>From a maintenance perspective, this is a significant advantage: if you understand the UI, you understand the export.</p><h2 id="download-mechanics-in-vaadin-button--download">Download mechanics in Vaadin: Button ≠ Download</h2><p>A common mistake in Vaadin applications is trying to start a file download directly from a button click. Technically, this is problematic: a button click primarily triggers server-side logic, whereas a download is a resource from the browser&rsquo;s perspective.</p><p>In Vaadin, a button click is primarily a<strong>server-side UI event</strong>. The browser does not send a &ldquo;classic&rdquo; download request; instead, Vaadin processes the click via its<strong>UI/RPC communication</strong> (server round-trip, event listener, component update). From the browser&rsquo;s perspective, this is<strong>not</strong> a normal navigation or resource retrieval. And that&rsquo;s exactly why &ldquo;button clicks → browser downloads file&rdquo; is not reliable, because the browser typically only starts a download cleanly when it retrieves a<strong>resource</strong> (link/navigation) or submits a form – i.e. something that is perceived in the browser as a &ldquo;real request for a file&rdquo;.</p><p>The anchor (<code>&lt;a&gt;</code>) element solves this problem because it is a<strong>standard download target</strong> for the browser: it has an href attribute that points to a resource, and the download attribute signals to the browser: &ldquo;This is a file&rdquo;. In Vaadin, you bind this href to a StreamResource. This creates a<strong>separate HTTP request</strong> when clicking the anchor, which is not part of the Vaadin UI event flow but rather an independent resource retrieval. Only at this moment is the StreamResource &ldquo;pulled&rdquo;, and the export content<strong>is generated</strong> on demand.</p><p>In practice, this has three major advantages:</p><ol><li><strong>Browser compliance and reliability:</strong> The download is started via a mechanism that the browser natively supports. This reduces edge cases in which a download triggered by a UI event is blocked or behaves inconsistently (e.g., pop-up/download policies, timing, UI updates).</li><li><strong>Decoupling from the UI lifecycle:</strong> The download occurs in a separate request. Even if Vaadin processes UI requests in parallel, if the user clicks on or rerenders the interface, the download can continue to run stably. This is especially important if export generation takes longer or is streamed.</li><li><strong>Clean accountability:</strong> The button is purely UI/UX (icon, tooltip, permissions, enable/disable, visual feedback). The anchor is purely &ldquo;transport&rdquo; (browser download). The StreamResource is purely a &ldquo;data supplier&rdquo; (the export is generated only when needed). This separation makes the code more maintainable and reduces the side effects.</li></ol><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">btnExport</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">DOWNLOAD</span><span class="p">.</span><span class="na">create</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnExport</span><span class="p">.</span><span class="na">setTooltipText</span><span class="p">(</span><span class="s">"Export current result set as ZIP"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnExport</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">exportAnchor</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">callJsFunction</span><span class="p">(</span><span class="s">"click"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">);</span></span></span></code></pre></div></div><p>The actual download behaviour is in the anchor connected to a StreamResource:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">StreamResource</span><span class="w"/><span class="n">exportResource</span><span class="w"/><span class="o">=</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">new</span><span class="w"/><span class="n">StreamResource</span><span class="p">(</span><span class="s">"export.zip"</span><span class="p">,</span><span class="w"/><span class="p">()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">UrlMappingListRequest</span><span class="w"/><span class="n">filter</span><span class="w"/><span class="o">=</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">searchBar</span><span class="p">.</span><span class="na">buildFilter</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">chunkSize</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="n">urlShortenerClient</span><span class="p">.</span><span class="na">exportAllAsZipDownload</span><span class="p">(</span><span class="n">filter</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">exportAnchor</span><span class="p">.</span><span class="na">setHref</span><span class="p">(</span><span class="n">exportResource</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">exportAnchor</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"download"</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span></span></span></code></pre></div></div><p>This pattern clearly separates the responsibilities: the UI interaction is limited to the button, which serves exclusively as a trigger for export. The browser download is triggered via the anchor element and is therefore treated as a regular resource request. Finally, the data is made available via the StreamResource, which only generates the export content when it is actually downloaded.</p><p>The export is only generated when the browser actually retrieves the resource – not when the user clicks on it.</p><h2 id="streamresource-export-on-demand-instead-of-in-advance">StreamResource: Export on demand instead of in advance</h2><p>The use of StreamResource is not a detail, but a deliberate architectural decision. The export is generated on demand while the browser reads the stream.</p><p>This has several advantages. On the UI side, the memory footprint remains low because the export does not need to be fully pre-generated and buffered. At the same time, the UI thread is not blocked because the data transfer occurs outside the regular UI lifecycle. The download can continue regardless of the current UI state, even if the user navigates or performs further actions during this time. If errors occur during stream generation, they can be propagated cleanly via a separate HTTP request without causing the UI state to become inconsistent.</p><p>The export is thus technically decoupled from the UI lifecycle, although it is logically triggered by the UI.</p><h2 id="paging-boundaries-as-a-protective-mechanism">Paging boundaries as a protective mechanism</h2><p>Another explicitly UI-related aspect of the export implementation is the deliberate limit on export quantity. The export uses the same chunkSize as the grid in the interface and is additionally limited by a fixed upper limit. This decision ensures that the export always remains within a clearly defined framework and can be derived directly from the current UI state.</p><p>From an architectural perspective, this limitation prevents the export from processing large amounts of data in an uncontrolled manner when a user triggers an export. Especially in Vaadin applications, where UI interactions are typically synchronous with server-side logic, this protective measure is crucial. It reduces the risk of heavy memory loads, long runtimes, or blocking operations that could negatively impact other users or the entire server.</p><p>At the same time, the paging boundary conveys a clear technical semantics to the outside world. The export is deliberately defined as an image of the currently visible result set. It mirrors exactly what the user sees in the grid, including filtering, sorting, and paging configurations. This does not imply a claim to completeness, as is typically associated with a backup.</p><p>This clarity is particularly relevant for user expectations. The export does not provide a complete system print or a historically complete data set, but a specifically selected excerpt. The limitation makes this character explicit and prevents misinterpretations, such as assuming that an export can fully restore the system.</p><p>From a maintenance and operations perspective, the paging boundary also serves as a natural safety line. It forces us to consciously design export scenarios and, if necessary, to provide separate mechanisms for backups or mass data withdrawals. As a result, the export remains a controllable UI tool and does not insidiously become an infrastructural backdoor for unlimited data queries.</p><p>In summary, limiting export volume is not a technical constraint but a deliberate design decision. It combines UI state, user expectations and system stability into a consistent overall picture and underlines once again that the export in the URL shortener is understood as a UI-driven result set – and expressly not as a substitute for a backup.</p><h3 id="real-json-export-from-the-running-system">Real JSON export from the running system</h3><p>The architectural decisions described above are particularly well understood from a real export of the running system. The following JSON export was generated directly from the Vaadin interface and represents a specific UI state at a defined point in time.</p><p>Even at the top level, the export contains all the necessary contextual information to enable independent classification. The formatVersion field explicitly defines the export format version, providing a stable foundation for future extensions. Changes to the internal data model do not automatically propagate to the export contract, provided the version limit is respected.</p><p>The field mode is deliberately chosen to speak. The filtered value makes it unmistakably clear that this is not a complete data deduction, but a result set restricted by UI filters. This information is crucial because it prevents the export from being mistakenly interpreted as a backup. The export does not capture the entire system state; it only includes the section the user has seen in the grid.</p><p>With exportedAt, the exact time of snapshot creation is recorded. The export thus clearly refers to a defined system state. Later changes to individual data records are deliberately not included and can be clearly delineated on the basis of this time stamp. This context is supplemented by the total field, which indicates the number of exported data records and enables a quick plausibility check without analysing the actual user data.</p><p>The actual technical data is located exclusively in the items array. Each entry describes a single URL-mapping dataset, including subject-relevant properties such as shortCode, originalUrl, and active, as well as temporal attributes createdAt and, optionally, expiresAt. It is notable that these objects contain no UI or export-specific metadata. They are deliberately reduced to the technical core and could also come from other contexts in the same form.</p><p>It is precisely this clear separation between top-level metadata and functional user data in the items array that makes the export an explainable artefact in itself. Even without knowledge of the internal code or the Vaadin interface, it is possible to determine when the export was created, under what conditions, its scope, and where the actual technical data begins.</p><p>The real export thus confirms the design goals described above. It is reproducible, rich in context and clearly recognisable as a UI-driven result set. Instead of merely transporting data, it also conveys its meaning and context of creation – a property that is crucial for maintainability, analysis and long-term further processing.</p><h2 id="effects-on-maintainability-and-comprehensibility">Effects on maintainability and comprehensibility</h2><p>The tight coupling between the export and the UI state ensures behaviour that is predictable for developers and users alike. The export follows the same rules as the grid display and contains no hidden special paths or implicit deviations. As a result, the export automatically evolves with the UI: any adjustment to filters, sorting, or paging mechanisms has a consistent effect on both paths without requiring additional synchronisation code.</p><p>From a developer&rsquo;s perspective, this architecture significantly reduces cognitive load. There is no separate mental model space for exporting, as its behaviour can be completely derived from the known UI state. If you understand the grid and its filter logic, you automatically understand the export. This not only simplifies onboarding new developers but also reduces the risk of unintentional inconsistencies during refactorings or functional enhancements.</p><p>Testability also benefits directly from this clarity. Since the export has no state and relies on stable request and response structures, it can be tested in isolation. Tests can be run with specific filter combinations and validate the resulting exports without simulating the entire UI or complex interaction sequences. At the same time, UI tests remain lean because they can focus on correctly generating the filter state.</p><p>In the long term, this structure improves the maintainability of the overall system. Changes to the UI do not introduce hidden side effects in the export, and conversely, further development of the export does not require parallel adjustments elsewhere. The risk of divergent logic paths between display and export is not only reduced but systematically eliminated.</p><p>In summary, the close integration of UI state and export logic ensures that export is not a special case in the system. It becomes a transparent, explainable, long-term, and maintainable component of the application that fits seamlessly into the existing Vaadin architecture.</p><h2 id="conclusion">Conclusion</h2><p>The export in the URL shortener is not an isolated API endpoint, but an integral part of the Vaadin UI architecture. It follows the same rules as the grid, uses the same filters and respects the same boundaries.</p><p>Vaadin Flow applications in particular show that a cleanly integrated export is less a question of the file format – and much more a question of clear responsibilities, explicit contracts and a consistently conceived UI workflow.</p>
]]></content:encoded><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2026/02/ChatGPT-Image-5.-Feb.-2026-16_08_32.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2026/02/ChatGPT-Image-5.-Feb.-2026-16_08_32.jpeg"/><enclosure url="https://svenruppert.com/images/2026/02/ChatGPT-Image-5.-Feb.-2026-16_08_32.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - Extracting Components - Part 2</title><link>https://svenruppert.com/posts/advent-calendar-2025-extracting-components-part-2/</link><pubDate>Mon, 22 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-extracting-components-part-2/</guid><description>What has happened so far In the first part, the focus was deliberately on the user interface&rsquo;s structural realignment. The previously grown, increasingly monolithic OverviewView was analysed and specifically streamlined by outsourcing key functional areas to independent UI components. With the introduction of the BulkActionsBar and the SearchBar, clearly defined building blocks were created, each assuming a specific responsibility and freeing the view from operational details.</description><content:encoded>&lt;![CDATA[<h2 id="what-has-happened-so-far">What has happened so far</h2><p>In the first part, the focus was deliberately on the user interface&rsquo;s structural realignment. The previously grown, increasingly monolithic OverviewView was analysed and specifically streamlined by outsourcing key functional areas to independent UI components. With the introduction of the BulkActionsBar and the SearchBar, clearly defined building blocks were created, each assuming a specific responsibility and freeing the view from operational details.</p><p>This refactoring was not a cosmetic step but a conscious investment in the application&rsquo;s long-term maintainability. By separating presentation, interaction, and logic, a modular foundation was created that is not only easier to test but also significantly simplifies future extensions. The OverviewView transformed from a function-overloaded central element into an orchestrating instance that brings components together instead of controlling their internal processes.</p><p>The second part now builds upon this foundation. While Part 1 highlighted the motivation, goals, and initial extractions, the focus now shifts consistently to the new structure of the OverviewView itself: how its role has changed, how event flows have been simplified and how the interaction of the extracted components leads to a clearer, more stable architecture.</p><p><strong>The source code for this article can be found on GitHub at:</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-11">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-11</a></p><p>Here is a screenshot of the current development status from the user&rsquo;s perspective.</p><p><figure><img src="/images/2025/12/image-71.png" alt="Overview page of a URL shortener application, featuring options to search, filter, and manage short links with corresponding URLs and actions." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-70.png" alt="Screenshot of the URL Shortener application interface, showing the ‘Create new short links’ section with input fields for target URL, expiration date, and aliases. The section includes buttons to save or reset the form and validations for the aliases, displaying their status as valid or invalid." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-72.png" alt="Screenshot of a URL shortener application overview, displaying a functional user interface with options for search filters, pagination controls, and a data table showing shortcodes, original URLs, creation dates, active states, and actions." loading="lazy" decoding="async"/></p><p>The focus is on dividing the previously monolithic OverviewView into clearly defined, reusable components such as the BulkActionsBar and the SearchBar. This separation improves maintainability, increases clarity and makes it easier to test individual functional areas. At the same time, this will pave the way for further UI optimisations and interaction patterns to be implemented gradually in the coming days.</p><h2 id="new-structure-of-the-overviewview">New structure of the OverviewView</h2><p>After removing the subcomponents, the new OverviewView appears in a much more streamlined, clearer form. While previously a large number of elements, event handlers and logic fragments were spread across the entire class, the View is now limited to a few, clearly defined tasks: the initialisation of the core components, the setup of the grid and the orchestration of the interactions between the SearchBar, BulkActionsBar and the backend.</p><p>This new structure follows a simple but powerful principle: the OverviewView focuses on bringing the components together rather than on how they work internally. It defines which building blocks are displayed, how they work together, and when they need to be updated. The internal structure of the individual elements – whether it be the handling of filter changes, the management of bulk actions, or the technical logic of the DataProvider – lies entirely within the respective components.</p><p>This clear layout makes the OverviewView look much tidier. The previously lush sections, with interspersed event listeners, complex UI layouts, and manual error handling, have largely disappeared, either merged into outsourced components or removed. They are replaced by short, easy-to-understand methods that control the flow of the view and connect its various building blocks.</p><p>This reduction not only makes the OverviewView easier to maintain but also easier to expand. New functions can be integrated at appropriate points without risking damage to existing logic. At the same time, an easily testable structure is created, as the dependencies are clearly defined and the responsibilities are clearly separated.</p><p>A central element of the new structure is the initialisation of the view, which is now clearly structured and largely free of embedded logic. This new distribution of roles can already be clearly seen in the constructor:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">public</span><span class="n">OverviewView</span><span class="p">()</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">setSizeFull</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">setPadding</span><span class="p">(</span><span class="k">true</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">setSpacing</span><span class="p">(</span><span class="k">true</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">add</span><span class="p">(</span><span class="n">new</span><span class="n">H2</span><span class="p">(</span><span class="s2">"URL Shortener – Overview"</span><span class="p">));</span></span></span><span class="line"><span class="cl"><span class="n">initDataProvider</span><span class="p">();</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">pagingBar</span><span class="p">=</span><span class="n">new</span><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">prevBtn</span><span class="p">,</span><span class="n">nextBtn</span><span class="p">,</span><span class="n">pageInfo</span><span class="p">,</span><span class="n">btnSettings</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">pagingBar</span><span class="p">.</span><span class="n">setDefaultVerticalComponentAlignment</span><span class="p">(</span><span class="n">CENTER</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">HorizontalLayout</span><span class="n">bottomBar</span><span class="p">=</span><span class="n">new</span><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">new</span><span class="n">Span</span><span class="p">(),</span><span class="n">pagingBar</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">bottomBar</span><span class="p">.</span><span class="n">setWidthFull</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">bottomBar</span><span class="p">.</span><span class="n">expand</span><span class="p">(</span><span class="n">bottomBar</span><span class="p">.</span><span class="n">getComponentAt</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span></span></span><span class="line"><span class="cl"><span class="n">bottomBar</span><span class="p">.</span><span class="n">setAlignItems</span><span class="p">(</span><span class="n">CENTER</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">VerticalLayout</span><span class="n">container</span><span class="p">=</span><span class="n">new</span><span class="n">VerticalLayout</span><span class="p">(</span><span class="n">searchBar</span><span class="p">,</span><span class="n">bottomBar</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">container</span><span class="p">.</span><span class="n">setPadding</span><span class="p">(</span><span class="k">false</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">container</span><span class="p">.</span><span class="n">setSpacing</span><span class="p">(</span><span class="k">true</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">container</span><span class="p">.</span><span class="n">setWidthFull</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">add</span><span class="p">(</span><span class="n">container</span><span class="p">);</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">add</span><span class="p">(</span><span class="n">bulkBar</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">add</span><span class="p">(</span><span class="n">grid</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">configureGrid</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">addListeners</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">addShortCuts</span><span class="p">();</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">try</span><span class="p">(</span><span class="k">var</span><span class="py">_</span><span class="p">=</span><span class="n">withRefreshGuard</span><span class="p">(</span><span class="k">false</span><span class="p">))</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">searchBar</span><span class="p">.</span><span class="n">setPageSize</span><span class="p">(</span><span class="mi">25</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">searchBar</span><span class="p">.</span><span class="n">setSortBy</span><span class="p">(</span><span class="s2">"createdAt"</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">searchBar</span><span class="p">.</span><span class="n">setDirValue</span><span class="p">(</span><span class="s2">"desc"</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">catch</span><span class="p">(</span><span class="n">Exception</span><span class="n">e</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">throw</span><span class="n">new</span><span class="n">RuntimeException</span><span class="p">(</span><span class="n">e</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here it becomes clear: The OverviewView only instantiates its components, inserts them into a layout and delegates all details to specialised classes. Neither filter logic nor bulk operations are included here – the view only orchestrates.</p><p>Another example of the streamlined structure is the way selection is handled in the grid. In the past, there was extensive logic for buttons, states and actions here; today, the view only controls the visibility and status of the BulkActionsBar:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="n">addSelectionListener</span><span class="p">(</span><span class="n">event</span><span class="o">-&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">all</span><span class="p">=</span><span class="n">event</span><span class="p">.</span><span class="n">getAllSelectedItems</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">boolean</span><span class="n">hasSelection</span><span class="p">=</span><span class="p">!</span><span class="n">all</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">();</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">setVisible</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">int</span><span class="n">count</span><span class="p">=</span><span class="n">all</span><span class="p">.</span><span class="n">size</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">String</span><span class="n">label</span><span class="p">=</span><span class="n">count</span><span class="o">==</span><span class="mi">1</span><span class="p">?</span><span class="s2">"link selected"</span><span class="p">:</span><span class="s2">"links selected"</span><span class="p">;</span></span></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">selectionInfoText</span><span class="p">(</span><span class="n">count</span><span class="p">+</span><span class="s2">" "</span><span class="p">+</span><span class="n">label</span><span class="p">+</span><span class="s2">" on page "</span><span class="p">+</span><span class="n">currentPage</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">selectionInfoText</span><span class="p">(</span><span class="s2">""</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">setButtonsEnabled</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>So the view only handles showing or hiding the BulkActionsBar. The actual logic for executing the actions lies entirely in the component itself.</p><p>The refresh mechanic is now clearly defined as well. Instead of repeating refresh calls in many places in the code, the<code>safeRefresh()</code> method has been created, which centrally defines how updates are performed:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">safeRefresh</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"safeRefresh"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">suppressRefresh</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"refresh"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dataProvider</span><span class="p">.</span><span class="na">refreshAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This design not only makes updating cleaner, but also prevents duplicate refreshes and unwanted infinite loops.</p><p>The new clarity is also reflected in the configuration of the grid, which remains complex in terms of content, but is clearly delineated and outsourced to its own methods:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private void configureGrid() {</span></span><span class="line"><span class="cl"> logger().info("configureGrid..");</span></span><span class="line"><span class="cl"> grid.addThemeVariants(GridVariant.LUMO_ROW_STRIPES, GridVariant.LUMO_COMPACT);</span></span><span class="line"><span class="cl"> grid.setHeight("70vh");</span></span><span class="line"><span class="cl"> grid.setSelectionMode(Grid.SelectionMode.MULTI);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> configureColumShortCode();</span></span><span class="line"><span class="cl"> configureColumUrl();</span></span><span class="line"><span class="cl"> configureColumCreated();</span></span><span class="line"><span class="cl"> configureColumActive();</span></span><span class="line"><span class="cl"> configureColumExpires();</span></span><span class="line"><span class="cl"> configureColumActions();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> grid.addItemDoubleClickListener(ev -&gt; openDetailsDialog(ev.getItem()));</span></span><span class="line"><span class="cl"> grid.addItemClickListener(ev -&gt; {</span></span><span class="line"><span class="cl"> if (ev.getClickCount() == 2) openDetailsDialog(ev.getItem());</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> GridContextMenu<span class="nt">&lt;ShortUrlMapping&gt;</span> menu = new GridContextMenu<span class="err">&lt;</span>&gt;(grid);</span></span><span class="line"><span class="cl"> menu.addItem("Show details", e -&gt; e.getItem().ifPresent(this::openDetailsDialog));</span></span><span class="line"><span class="cl"> menu.addItem("Open URL", e -&gt; e.getItem().ifPresent(m -&gt; UI.getCurrent().getPage().open(m.originalUrl(), "_blank")));</span></span><span class="line"><span class="cl"> menu.addItem("Copy shortcode", e -&gt; e.getItem().ifPresent(m -&gt; UI.getCurrent().getPage().executeJs("navigator.clipboard.writeText($0)", m.shortCode())));</span></span><span class="line"><span class="cl"> menu.addItem("Delete...", e -&gt; e.getItem().ifPresent(m -&gt; confirmDelete(m.shortCode())));</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This structure makes it clear which areas of the view have which tasks. The multi-outsourced logic ensures that each method fulfils a clearly defined function.</p><h2 id="simplified-event-pipeline-after-refactoring">Simplified event pipeline after refactoring</h2><p>Refactoring has significantly reduced this complexity. The event pipeline now follows a linear, predictable flow: users interact with the UI components, which trigger clearly defined actions; the OverviewView responds with manageable control signals and finally updates the grid via a uniform refresh mechanism. Critical is the introduction of<code>safeRefresh()</code> and<code>withRefreshGuard(),</code> which prevent unnecessary or recursive updates.</p><p>The result is an architecture in which events are no longer propagated across the view, but run along a handful of defined interfaces in a structured manner. This is already clearly visible in the central listener setup of the OverviewView:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">addListeners</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ComponentUtil</span><span class="p">.</span><span class="na">addListener</span><span class="p">(</span><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">MappingCreatedOrChanged</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Received MappingCreatedOrChanged -&gt; refreshing overview"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refreshPageInfo</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">addSelectionListener</span><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">all</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">event</span><span class="p">.</span><span class="na">getAllSelectedItems</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">hasSelection</span><span class="w"/><span class="o">=</span><span class="w"/><span class="o">!</span><span class="n">all</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="na">setVisible</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">hasSelection</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">count</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">all</span><span class="p">.</span><span class="na">size</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">label</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">count</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">1</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"link selected"</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"links selected"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="na">selectionInfoText</span><span class="p">(</span><span class="n">count</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">label</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" on page "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">currentPage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="na">selectionInfoText</span><span class="p">(</span><span class="s">""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="na">setButtonsEnabled</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">btnSettings</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ColumnVisibilityDialog</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">grid</span><span class="p">,</span><span class="w"/><span class="n">columnVisibilityService</span><span class="p">).</span><span class="na">open</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">prevBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">currentPage</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">1</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="o">--</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refreshPageInfo</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">nextBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">searchBar</span><span class="p">.</span><span class="na">getPageSize</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="n">25</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">maxPage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">ceil</span><span class="p">((</span><span class="kt">double</span><span class="p">)</span><span class="w"/><span class="n">totalCount</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">size</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">currentPage</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">maxPage</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refreshPageInfo</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This is where the new clarity becomes apparent: External events such as<code>MappingCreatedOrChanged</code> trigger a targeted refresh; the grid selection only affects the BulkActionsBar; the paging buttons only control page and refresh. The complex logic from earlier days is clearly divided.</p><p>The keyboard shortcuts also integrate seamlessly into this simplified pipeline and use the encapsulated bulk logic:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">addShortCuts</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">current</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">current</span><span class="p">.</span><span class="na">addShortcutListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">grid</span><span class="p">.</span><span class="na">getSelectedItems</span><span class="p">().</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="na">confirmBulkDeleteSelected</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">},</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Key</span><span class="p">.</span><span class="na">DELETE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>On the SearchBar side, event processing is also greatly relieved and follows a clear pattern. Changes to filters or settings always lead to the OverviewView and its uniform refresh mechanism:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">activeState</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">setCurrentPage</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">pageSize</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">setCurrentPage</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">setGridPageSize</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">resetBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="kd">var</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">withRefreshGuard</span><span class="p">(</span><span class="kc">true</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">resetElements</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">setCurrentPage</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RuntimeException</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>The use of<code>withRefreshGuard(true)</code> in combination with<code>safeRefresh()</code> ensures that certain internal state changes do not immediately trigger a cascade refresh, but are triggered in a controlled, conscious manner.</p><h2 id="improvements-in-the-multialiaseditorstrict">Improvements in the MultiAliasEditorStrict</h2><p>The MultiAliasEditorStrict is a central UI element in the URL shortener, allowing you to edit and manage multiple alias variants of a shortlink. During the refactoring, this editor was also revised to better integrate with the new component architecture and, at the same time, improve the user experience. Many of the original challenges resulted from tight integration with the OverviewView and from a poorly structured internal logic that had grown over the course of development.</p><p>One of the most essential innovations concerns the consistency of his behaviour. The MultiAliasEditorStrict is implemented as a standalone, clearly focused UI element that is solely responsible for capturing and validating aliases:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class MultiAliasEditorStrict</span></span><span class="line"><span class="cl"> extends VerticalLayout {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private static final String RX = "^[A-Za-z0-9_-]{3,64}$";</span></span><span class="line"><span class="cl"> private final Grid<span class="nt">&lt;Row&gt;</span> grid = new Grid<span class="err">&lt;</span>&gt;(Row.class, false);</span></span><span class="line"><span class="cl"> private final TextArea bulk = new TextArea("Aliases (comma/space/newline)");</span></span><span class="line"><span class="cl"> private final Button insertBtn = new Button("Take over");</span></span><span class="line"><span class="cl"> private final Button validateBtn = new Button("Validate all");</span></span><span class="line"><span class="cl"> private final String baseUrl;</span></span><span class="line"><span class="cl"> private final Function<span class="nt">&lt;String</span><span class="err">,</span><span class="err">Boolean</span><span class="nt">&gt;</span> isAliasFree; Server check (true = free)</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public MultiAliasEditorStrict(String baseUrl,</span></span><span class="line"><span class="cl"> Function<span class="nt">&lt;String</span><span class="err">,</span><span class="err">Boolean</span><span class="nt">&gt;</span> isAliasFree) {</span></span><span class="line"><span class="cl"> this.baseUrl = baseUrl;</span></span><span class="line"><span class="cl"> this.isAliasFree = isAliasFree;</span></span><span class="line"><span class="cl"> build();</span></span><span class="line"><span class="cl"> }</span></span></code></pre></div></div><p>It is already clear here that the editor has a well-defined task: it knows the preview<code>baseUrl</code> prefix, manages its own UI elements, and provides an<code>isAliasFree</code>function to check for alias conflicts on the server side.</p><p>The structurinterface&rsquo;s interface is entirely contained within the<code>build()</code> method. This is where text input, toolbar and grid are assembled:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">build</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setPadding</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setSpacing</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulk</span><span class="p">.</span><span class="na">setWidthFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulk</span><span class="p">.</span><span class="na">setMinHeight</span><span class="p">(</span><span class="s">"120px"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulk</span><span class="p">.</span><span class="na">setValueChangeMode</span><span class="p">(</span><span class="n">ValueChangeMode</span><span class="p">.</span><span class="na">LAZY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulk</span><span class="p">.</span><span class="na">setClearButtonVisible</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulk</span><span class="p">.</span><span class="na">setPlaceholder</span><span class="p">(</span><span class="s">"e.g.\nnews-2025\npromo_x\nabc123"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">insertBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">parseBulk</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">validateBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">validateAll</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">toolbar</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">insertBtn</span><span class="p">,</span><span class="w"/><span class="n">validateBtn</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">toolbar</span><span class="p">.</span><span class="na">setSpacing</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">configureGrid</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">bulk</span><span class="p">,</span><span class="w"/><span class="n">toolbar</span><span class="p">,</span><span class="w"/><span class="n">grid</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This makes the user interface clear: First, aliases are entered in the bulk field, then transferred to the grid via &ldquo;Take over&rdquo; and finally checked via &ldquo;Validate all&rdquo;.</p><p>The MultiAliasEditorStrict has also been improved in terms of structure and internal architecture. Instead of distributing logic fragments across validation, synchronisation, and UI updates in an unstructured way, these areas were clearly separated and moved to dedicated methods. This is particularly evident in the grid structure and in the validation logic.</p><p>The grid itself displays the alias lines, a preview and the status in a compact form:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">configureGrid</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">addComponentColumn</span><span class="p">(</span><span class="n">row</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">tf</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tf</span><span class="p">.</span><span class="na">setWidthFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tf</span><span class="p">.</span><span class="na">setMaxLength</span><span class="p">(</span><span class="n">64</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tf</span><span class="p">.</span><span class="na">setPattern</span><span class="p">(</span><span class="n">RX</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tf</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNullElse</span><span class="p">(</span><span class="n">row</span><span class="p">.</span><span class="na">getAlias</span><span class="p">(),</span><span class="w"/><span class="s">""</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tf</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="n">row</span><span class="p">.</span><span class="na">getStatus</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="n">Status</span><span class="p">.</span><span class="na">SAVED</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">tf</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">row</span><span class="p">.</span><span class="na">setAlias</span><span class="p">(</span><span class="n">ev</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">validateRow</span><span class="p">(</span><span class="n">row</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getDataProvider</span><span class="p">().</span><span class="na">refreshItem</span><span class="p">(</span><span class="n">row</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">tf</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}).</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Alias"</span><span class="p">).</span><span class="na">setFlexGrow</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">addColumn</span><span class="p">(</span><span class="n">r</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">baseUrl</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNullElse</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="na">getAlias</span><span class="p">(),</span><span class="w"/><span class="s">""</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Preview"</span><span class="p">).</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">addComponentColumn</span><span class="p">(</span><span class="n">row</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">lbl</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">switch</span><span class="p">(</span><span class="n">row</span><span class="p">.</span><span class="na">getStatus</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">NEW</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"New"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">VALID</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"Valid"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">INVALID_FORMAT</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"Format"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">CONFLICT</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"Taken"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">ERROR</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"Error"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">SAVED</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"Saved"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">};</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">badge</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">lbl</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">theme</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">switch</span><span class="w"/><span class="p">(</span><span class="n">row</span><span class="p">.</span><span class="na">getStatus</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">VALID</span><span class="p">,</span><span class="w"/><span class="n">SAVED</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"badge success"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">CONFLICT</span><span class="p">,</span><span class="w"/><span class="n">INVALID_FORMAT</span><span class="p">,</span><span class="w"/><span class="n">ERROR</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"badge error"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">default</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"badge"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">};</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">badge</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">getThemeList</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="n">theme</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">row</span><span class="p">.</span><span class="na">getMsg</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">row</span><span class="p">.</span><span class="na">getMsg</span><span class="p">().</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="n">badge</span><span class="p">.</span><span class="na">setTitle</span><span class="p">(</span><span class="n">row</span><span class="p">.</span><span class="na">getMsg</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">badge</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}).</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Status"</span><span class="p">).</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">addComponentColumn</span><span class="p">(</span><span class="n">row</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">del</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"✕"</span><span class="p">,</span><span class="w"/><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">items</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">grid</span><span class="p">.</span><span class="na">getListDataView</span><span class="p">().</span><span class="na">getItems</span><span class="p">().</span><span class="na">toList</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">items</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="n">row</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">setItems</span><span class="p">(</span><span class="n">items</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">del</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="s">"Remove"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">del</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}).</span><span class="na">setHeader</span><span class="p">(</span><span class="s">""</span><span class="p">).</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">setItems</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">setAllRowsVisible</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">setHeight</span><span class="p">(</span><span class="s">"320px"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here, it is clearly visible how all relevant information – alias, preview URL, status, and deletion action – converges in a separate, self-contained table. The status display plays a central role in providing the user with feedback on format errors, conflicts, successful validation or entries that have already been saved.</p><p>The real intelligence of the editor lies in the<code>parseBulk()</code> and<code>validateRow()</code> logic.<code>parseBulk()</code> takes over the task of processing the free text input, detecting duplicates and creating new lines in the grid:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private void parseBulk() {</span></span><span class="line"><span class="cl"> var text = Objects.requireNonNullElse(bulk.getValue(), "");</span></span><span class="line"><span class="cl"> var tokens = Arrays.stream(text.split("[,;\\s]+"))</span></span><span class="line"><span class="cl"> .map(String::trim)</span></span><span class="line"><span class="cl"> .filter(s -&gt; !s.isBlank())</span></span><span class="line"><span class="cl"> .distinct()</span></span><span class="line"><span class="cl"> .toList();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> if (tokens.isEmpty()) {</span></span><span class="line"><span class="cl"> Notification.show("No aliases to insert", 2000, Notification.Position.TOP_CENTER);</span></span><span class="line"><span class="cl"> return;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Set<span class="nt">&lt;String&gt;</span> existing = grid.getListDataView().getItems()</span></span><span class="line"><span class="cl"> .map(Row::getAlias)</span></span><span class="line"><span class="cl"> .filter(Objects::nonNull)</span></span><span class="line"><span class="cl"> .collect(Collectors.toCollection(() -&gt; new TreeSet<span class="err">&lt;</span>&gt;(String.CASE_INSENSITIVE_ORDER)));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var view = grid.getListDataView();</span></span><span class="line"><span class="cl"> int added = 0;</span></span><span class="line"><span class="cl"> for (String tok : tokens) {</span></span><span class="line"><span class="cl"> if (existing.contains(tok)) continue;</span></span><span class="line"><span class="cl"> var r = new Row(tok);</span></span><span class="line"><span class="cl"> validateRow(r);</span></span><span class="line"><span class="cl"> view.addItem(r);</span></span><span class="line"><span class="cl"> existing.add(tok);</span></span><span class="line"><span class="cl"> added++;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> grid.getDataProvider().refreshAll();</span></span><span class="line"><span class="cl"> bulk.clear();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Notification.show("Inserted: " + added, 2000, Notification.Position.TOP_CENTER);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The validation of individual rows is encapsulated in<code>validateRow()</code> and follows a clear step-by-step model of format checking, duplicate checking, and optional server querying:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">validateRow</span><span class="p">(</span><span class="n">Row</span><span class="w"/><span class="n">r</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">a</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNullElse</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="na">getAlias</span><span class="p">(),</span><span class="w"/><span class="s">""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">a</span><span class="p">.</span><span class="na">matches</span><span class="p">(</span><span class="n">RX</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="p">.</span><span class="na">INVALID_FORMAT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setMsg</span><span class="p">(</span><span class="s">"3–64: A–Z a–z 0–9 - _"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">same</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getListDataView</span><span class="p">().</span><span class="na">getItems</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">x</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">x</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="n">r</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="w"/><span class="n">x</span><span class="p">.</span><span class="na">getAlias</span><span class="p">()))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">count</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">same</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="p">.</span><span class="na">CONFLICT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setMsg</span><span class="p">(</span><span class="s">"Duplicate in list"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isAliasFree</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">isAliasFree</span><span class="p">.</span><span class="na">apply</span><span class="p">(</span><span class="n">a</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="p">.</span><span class="na">CONFLICT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setMsg</span><span class="p">(</span><span class="s">"Alias taken"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="p">.</span><span class="na">ERROR</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setMsg</span><span class="p">(</span><span class="s">"Check failed"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="p">.</span><span class="na">VALID</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setMsg</span><span class="p">(</span><span class="s">""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Together with the<code>status</code> enum and the inner<code>row</code> class, this results in a self-contained, easily comprehensible state machine for each alias:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">enum</span><span class="w"/><span class="n">Status</span><span class="w"/><span class="p">{</span><span class="w"/><span class="n">NEW</span><span class="p">,</span><span class="w"/><span class="n">VALID</span><span class="p">,</span><span class="w"/><span class="n">INVALID_FORMAT</span><span class="p">,</span><span class="w"/><span class="n">CONFLICT</span><span class="p">,</span><span class="w"/><span class="n">ERROR</span><span class="p">,</span><span class="w"/><span class="n">SAVED</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">Row</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">string</span><span class="w"/><span class="n">alias</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">status</span><span class="w"/><span class="n">status</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Status</span><span class="p">.</span><span class="na">NEW</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">msg</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">""</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Row</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">a</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">alias</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">a</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">getAlias</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">alias</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setAlias</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">a</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">alias</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">a</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">Status</span><span class="w"/><span class="nf">getStatus</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">status</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="w"/><span class="n">s</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">status</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">s</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">getMsg</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">msg</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setMsg</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">m</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">msg</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">m</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Overall, the revision of the MultiAliasEditorStrict shows how even smaller UI components can benefit significantly from a clean structure, clear responsibilities and consistent behaviour. The component is now more stable, more comprehensible and easier to integrate – ideal conditions for future extensions or adjustments.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Part2-02.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Part2-02.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Part2-02.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - Extracting Components - Part 1</title><link>https://svenruppert.com/posts/advent-calendar-2025-extracting-components-part-1/</link><pubDate>Sun, 21 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-extracting-components-part-1/</guid><description>Today marks a crucial step in the evolution of the URL shortener&rsquo;s user interface. After the focus in the past few days was mainly on functional enhancements – from filter and search functions to bulk operations – this day is dedicated to a structural fine-tuning: the refactoring of central UI components. This refactoring not only serves to clean up the code but also creates a clear, modular basis for future extensions as well as a significantly improved developer experience.</description><content:encoded>&lt;![CDATA[<p>Today marks a crucial step in the evolution of the URL shortener&rsquo;s user interface. After the focus in the past few days was mainly on functional enhancements – from filter and search functions to bulk operations – this day is dedicated to a structural fine-tuning: the refactoring of central UI components. This refactoring not only serves to clean up the code but also creates a clear, modular basis for future extensions as well as a significantly improved developer experience.</p><p><strong>The source code for this article can be found on GitHub at:</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-11">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-11</a></p><p>Here is a screenshot of the current development state from the user&rsquo;s perspective.</p><p><figure><img src="/images/2025/12/image-69.png" alt="Screenshot of a URL shortener interface showing the overview with a search bar, pagination controls, and a table of shortened URLs with their details such as shortcode, creation date, and active status." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-67.png" alt="A screenshot of a URL shortener interface showing the ‘Create new short links’ section. The left sidebar displays navigation options like Overview, Create, Youtube, and About. The main area includes fields for entering a target URL, expiry date, and time, along with action buttons for saving or resetting the form. Additionally, there’s a section for entering aliases with a table displaying existing aliases, their previews, and statuses." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-68.png" alt="Screenshot of the URL shortener overview interface, displaying fields for searching, filtering, and a table showing shortened URLs with related information such as creation date and active status." loading="lazy" decoding="async"/></p><p>The focus is on dividing the previously monolithic OverviewView into clearly defined, reusable components such as the BulkActionsBar and the SearchBar. This separation improves maintainability, increases clarity and makes it easier to test individual functional areas. At the same time, this will pave the way for further UI optimisations and interaction patterns to be implemented gradually in the coming days.</p><h3 id="motivation-for-refactoring">Motivation for refactoring</h3><p>As an application&rsquo;s functionality increases, so does its source code complexity. Especially in a UI that is continuously expanded and adapted to new requirements, this quickly leads to monolithic structures in which presentation, logic and state management are closely interwoven. This is precisely the situation that had developed in the OverviewView over the past few days of development: a central view that had to take on more and more tasks and thus became increasingly difficult to understand, extend, and test.</p><p>Refactoring today, therefore, starts at a fundamental point. The goal is not to introduce new features, but to create a clear, modular foundation that makes future expansions much easier. Individual UI building blocks are removed from the overloaded view and formed into independent components, with responsibility-specific tasks, clear communication channels, and significantly improved reusability. This not only reduces the complexity within the view itself, but also makes the entire architecture more robust to change.</p><p>At the same time, this refactoring strengthens the developer experience: The structure of the code becomes more comprehensible, components can be improved in isolation, and future changes – such as to the search logic or bulk operations – can be made in the right place in a targeted manner. The result is a UI design that is more stable, more flexible and easier to maintain in the long term.</p><h3 id="why-is-this-step-a-turning-point-today">Why is this step a turning point today</h3><p>Today marks a critical moment in the development of the URL shortener&rsquo;s user interface, as the focus shifts for the first time from new features to the code&rsquo;s structural quality. While the previous steps were primarily aimed at delivering visible improvements for the user, it is now a matter of ensuring the foundation on which these functions remain solid and scalable over the long term. Such refactoring creates clarity in areas that had previously grown instead of planned – and that&rsquo;s precisely what makes this day a turning point.</p><p>By separating the central UI building blocks, an architecture emerges that no longer consists of a single, heavyweight view but of clearly defined components that perform precisely defined tasks. This structural realignment opens up new possibilities for expansion, halts progress on technical debt reduction, and ensures that upcoming features no longer have to be integrated into a confusing block. Instead, each feature can be implemented in the right place without destabilising existing areas.</p><p>This step thus makes a decisive contribution to long-term development: reduced friction in the code, fewer side effects, and greater clarity. So today it&rsquo;s not about visible innovations, but about the quality of the foundation – and that&rsquo;s exactly what makes working on upcoming expansions much more efficient, stable and enjoyable.</p><h3 id="overview-of-the-most-important-changes">Overview of the most important changes</h3><p>Today, the focus is on replacing the previously central<code>OverviewView</code>, which had increasingly assumed responsibility over the course of development and had become correspondingly confusing. By extracting independent components, this complexity is decomposed into clearly defined building blocks.</p><p>An essential part of this restructuring is the introduction of the new<code>BulkActionsBar</code>, which bundles all mass operations, making both the code and the user interface more straightforward. The new<code>SearchBar</code> also creates a dedicated area that includes search and filter functions, making it easier to expand. Both components not only simplify the view but also provide a foundation for consistent design and recurring interaction patterns throughout the user interface.</p><p>In addition, the internal logic of the overview has been streamlined: state management, event handling, and grid interactions have been rearranged to reduce unwanted dependencies and enable targeted future changes. Minor improvements, such as the unified formatting of specific UI components, also contribute to the overall impression of a clean, structured codebase.</p><h2 id="why-are-ui-components-removed-from-views">Why are UI components removed from views?</h2><p>In mature user interfaces, a close interlocking of layout, interaction logic and state management often arises. As a result, central views become increasingly extensive over time and lose their original character. A clear entry point of the application becomes a complex block whose internal logic is difficult to understand. This is exactly where the separation of independent components comes in: It serves to clearly separate responsibilities and shift complexity to where it belongs – into small, clearly defined building blocks.</p><p>By extracting UI components, the view is reduced to its core task: orchestrating the interaction of several well-defined elements. Each component takes on a clearly defined role – be it managing search filters, triggering bulk operations, or displaying individual UI sections. This modular approach improves readability, fosters a more natural understanding of the architecture, and significantly reduces side effects during refactoring.</p><p>Another advantage is reusability. Components that are only loosely coupled to the overall view can be used flexibly elsewhere, expanded, or improved in isolation. This not only creates a robust structure but also enables more sustainable development practices, allowing new functions to be implemented without major interventions in existing areas. The separation of UI components is, therefore, an essential step in keeping a growing project stable and clear in the long term.</p><h2 id="the-new-bulkactionsbar">The new BulkActionsBar</h2><p>Bulk operations are a function that plays an increasingly important role in the daily use of a URL shortener. As the number of entries grows, the need to edit several items at the same time increases – for example, to deactivate expired links, delete outdated campaigns or manage a larger number of new entries together. Such operations are not only a convenience feature but also an essential part of efficient workflows.</p><p>In many projects, bulk operations are first integrated directly into the main view. This works well at first, but in the long run, it causes view overload. Buttons, health checks, and more complex interaction logics begin to blend between Grid and View. The result is a structure that is difficult to maintain and in which extensions always carry the risk of unwanted side effects.</p><p>The decision to outsource bulk operations to a separate component creates a clear separation: the<code>BulkActionsBar</code> takes over the entire UI-related part of mass actions and thus forms a dedicated functional area. It bundles all relevant actions in one place, ensures a consistent user experience, and reduces complexity in the higher-level view. This clear delineation makes it much easier to add new actions, extend existing ones, or adapt the presentation to future design requirements.</p><p>In this way, outsourcing bulk operations makes a decisive contribution to a more stable and flexible architecture – and ensures that the user interface remains clear and intuitive even as requirements grow.</p><h3 id="structure-of-the-bulkactionsbar-component">Structure of the BulkActionsBar component</h3><p>The BulkActionsBar is an independent UI component that bundles all mass actions and presents them in a clearly structured way. Their structure follows the principle that an element should cover exactly one area of responsibility: In this case, the initiation of collection actions without itself containing logic for data processing. This keeps it easy to understand and flexible to use.</p><p>The central component of the BulkActionsBar is a clearly defined collection of interaction elements – typically buttons or icons – each of which triggers a specific action. These elements are compactly arranged in a horizontal layout, making them highly visible and intuitively accessible in the user interface. This is complemented by a mechanism that controls the visibility and activability of actions based on whether and how many entries are selected in the grid. In this way, the component not only remains clear but also guides the user through clear interaction instructions.</p><p>Another essential aspect of the structure is the abstraction of event processing. The component itself does not perform any operations; instead, it signals to the higher-level view via events or callback functions which action to trigger. This creates loose coupling between the UI and the logic, which both facilitates testing and allows adjustments to individual areas without unintentionally affecting other components.</p><p>A practical example of this is the basic structure of the class that defines the BulkActionsBar:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class BulkActionsBar</span></span><span class="line"><span class="cl"> extends Composite<span class="nt">&lt;HorizontalLayout&gt;</span></span></span><span class="line"><span class="cl"> implements HasLogger {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private final URLShortenerClient urlShortenerClient;</span></span><span class="line"><span class="cl"> private final Grid<span class="nt">&lt;ShortUrlMapping&gt;</span> grid;</span></span><span class="line"><span class="cl"> private final OverviewView holdingComponent;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private final Button bulkDeleteBtn = new Button(new Icon(VaadinIcon.TRASH));</span></span><span class="line"><span class="cl"> private final Button bulkSetExpiryBtn = new Button(new Icon(VaadinIcon.CLOCK));</span></span><span class="line"><span class="cl"> private final Button bulkClearExpiryBtn = new Button(new Icon(VaadinIcon.CLOSE_CIRCLE));</span></span><span class="line"><span class="cl"> private final Button bulkActivateBtn = new Button(new Icon(VaadinIcon.PLAY));</span></span><span class="line"><span class="cl"> private final Button bulkDeactivateBtn = new Button(new Icon(VaadinIcon.STOP));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private final Span selectionInfo = new Span();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public BulkActionsBar(URLShortenerClient urlShortenerClient,</span></span><span class="line"><span class="cl"> Grid<span class="nt">&lt;ShortUrlMapping&gt;</span> grid,</span></span><span class="line"><span class="cl"> OverviewView holdingComponent) {</span></span><span class="line"><span class="cl"> this.urlShortenerClient = urlShortenerClient;</span></span><span class="line"><span class="cl"> this.grid = grid;</span></span><span class="line"><span class="cl"> this.holdingComponent = holdingComponent;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> buildBulkBar();</span></span><span class="line"><span class="cl"> addListeners();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Here, it becomes clear how the component encapsulates all elements relevant to mass actions: It knows the<code>URLShortenerClient</code>, the underlying<code>Grid&lt;ShortUrlMapping&gt;,</code> and the higher-level<code>OverviewView</code>, yet remains clearly delimited as an independent component. All buttons and the selection information area are defined within the class and are initialised in the constructor.</p><p>The actual visual structure of the BulkActionsBar is encapsulated in its own method:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">buildBulkBar</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">---</span><span class="w"/><span class="n">Common</span><span class="w"/><span class="n">style</span><span class="w"/><span class="o">---</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">().</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"background"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-contrast-5pct)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"padding"</span><span class="p">,</span><span class="w"/><span class="s">"0.4rem 0.8rem"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"border-radius"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-border-radius-m)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"border-bottom"</span><span class="p">,</span><span class="w"/><span class="s">"1px solid var(--lumo-contrast-20pct)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">---</span><span class="w"/><span class="n">button</span><span class="w"/><span class="n">setup</span><span class="w"/><span class="o">---</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setupIconButton</span><span class="p">(</span><span class="n">bulkDeleteBtn</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">TRASH</span><span class="p">,</span><span class="w"/><span class="s">"Delete selected links"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-error-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setupIconButton</span><span class="p">(</span><span class="n">bulkSetExpiryBtn</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CALENDAR_CLOCK</span><span class="p">,</span><span class="w"/><span class="s">"Set expiry for selected"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-primary-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setupIconButton</span><span class="p">(</span><span class="n">bulkClearExpiryBtn</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CALENDAR_CLOCK</span><span class="p">,</span><span class="w"/><span class="s">"Clear expiry for selected"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-secondary-text-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setupIconButton</span><span class="p">(</span><span class="n">bulkActivateBtn</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CHECK_CIRCLE</span><span class="p">,</span><span class="w"/><span class="s">"Activate selected"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-success-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setupIconButton</span><span class="p">(</span><span class="n">bulkDeactivateBtn</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CLOSE_CIRCLE</span><span class="p">,</span><span class="w"/><span class="s">"Deactivate selected"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-error-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">().</span><span class="na">removeAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">selectionInfo</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"opacity"</span><span class="p">,</span><span class="w"/><span class="s">"0.7"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">selectionInfo</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"font-size"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-font-size-s)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">selectionInfo</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"margin-right"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-space-m)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">selectionInfo</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkDeleteBtn</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkSetExpiryBtn</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkClearExpiryBtn</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkActivateBtn</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkDeactivateBtn</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">().</span><span class="na">setWidthFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">().</span><span class="na">setSpacing</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">().</span><span class="na">setDefaultVerticalComponentAlignment</span><span class="p">(</span><span class="n">FlexComponent</span><span class="p">.</span><span class="na">Alignment</span><span class="p">.</span><span class="na">CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">().</span><span class="na">setVisible</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The method shows how style, layout and interaction elements are brought together in a central place. The BulkActionsBar thus defines its own visual and functional cluster within the interface, while the URLShortenerClient and the OverviewView still handle the actual execution of the actions.</p><p>Overall, this setup enables a clean separation of display and behaviour, provides a robust foundation for future expansion, and integrates seamlessly with the application&rsquo;s modular architecture.</p><h3 id="using-the-bulkactionsbar-in-the-overviewview">Using the BulkActionsBar in the OverviewView</h3><p>The BulkActionsBar is only valuable for interaction with the OverviewView, because there it becomes clear how much outsourcing mass actions simplifies the user interface structure. While these actions were previously implemented directly in the view itself, mixing a large number of event handlers, UI elements, and logic, the BulkActionsBar now handles this entire functional area. The OverviewView only needs to define when the bar becomes visible, which entries are selected, and how to respond to the triggered actions.</p><p>The integration follows a clear pattern: As soon as the user selects one or more rows in the grid, the view displays the BulkActionsBar and passes it the current selection state. The component itself does not perform any operations, but signals which action has been triggered via clearly defined methods and events. This creates loose coupling, which increases clarity and significantly improves visibility.</p><p>Another advantage is evident when handling status changes. The OverviewView no longer has to activate or deactivate buttons individually or control complex UI dependencies. Instead, a single central feedback to the BulkActionsBar is sufficient; it updates itself. This interaction not only makes the code leaner, but also prevents errors that could easily occur with multiple scattered logics.</p><p>A central entry point for the integration is to initialise the BulkActionsBar directly as a field of the OverviewView:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">BulkActionsBar</span><span class="w"/><span class="n">bulkBar</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">BulkActionsBar</span><span class="p">(</span><span class="n">urlShortenerClient</span><span class="p">,</span><span class="w"/><span class="n">grid</span><span class="p">,</span><span class="w"/><span class="k">this</span><span class="p">);</span></span></span></code></pre></div></div><p>This provides the view with a fully configured instance that knows the<code>URLShortenerClient</code>, the grid, and the view itself. The component is then integrated into the constructor:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">add</span><span class="p">(</span><span class="n">bulkBar</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">add</span><span class="p">(</span><span class="n">grid</span><span class="p">);</span></span></span></code></pre></div></div><p>Significant is the interaction with the grid’s selection listener. Here, the view decides whether the BulkActionsBar is visible and what information it should display:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="n">addSelectionListener</span><span class="p">(</span><span class="n">event</span><span class="o">-&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">all</span><span class="p">=</span><span class="n">event</span><span class="p">.</span><span class="n">getAllSelectedItems</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">boolean</span><span class="n">hasSelection</span><span class="p">=</span><span class="p">!</span><span class="n">all</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">();</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">setVisible</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">int</span><span class="n">count</span><span class="p">=</span><span class="n">all</span><span class="p">.</span><span class="n">size</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">String</span><span class="n">label</span><span class="p">=</span><span class="n">count</span><span class="o">==</span><span class="mi">1</span><span class="p">?</span><span class="s2">"link selected"</span><span class="p">:</span><span class="s2">"links selected"</span><span class="p">;</span></span></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">selectionInfoText</span><span class="p">(</span><span class="n">count</span><span class="p">+</span><span class="s2">" "</span><span class="p">+</span><span class="n">label</span><span class="p">+</span><span class="s2">" on page "</span><span class="p">+</span><span class="n">currentPage</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">selectionInfoText</span><span class="p">(</span><span class="s2">""</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">setButtonsEnabled</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>This section clarifies the precise distribution of roles: the view recognises the current selection state, updates the visibility of the BulkActionsBar, and forwards relevant status information. The component itself remains completely free of logic for selection management.</p><p>The triggering of the actual actions also remains bundled in the view. An example of this is deleting using keyboard shortcuts:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">current</span><span class="p">.</span><span class="n">addShortcutListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="err">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">grid</span><span class="p">.</span><span class="n">getSelectedItems</span><span class="p">().</span><span class="n">isEmpty</span><span class="p">())</span><span class="w"/><span class="err">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="n">confirmBulkDeleteSelected</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="err">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="err">}</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">Key</span><span class="p">.</span><span class="k">DELETE</span><span class="p">);</span></span></span></code></pre></div></div><p>This once again shows the interplay of clearly defined responsibilities: the BulkActionsBar encapsulates the action UI, and the OverviewView determines the context in which it is executed.</p><p>Overall, the use of the BulkActionsBar in the OverviewView clearly demonstrates how a clear separation of responsibilities improves the architecture of a Vaadin application. The View refocuses on its core task – presenting and updating the data – while the component encapsulates and consistently delivers all the interaction around mass actions. The BulkActionsBar in the OverviewView demonstrates how a clear separation of responsibilities improves the architecture of a Vaadin application. The View refocuses on its core task – presenting and updating the data – while the component encapsulates and consistently delivers all the interaction around mass actions.</p><h3 id="code-comparison-before-vs-after">Code Comparison: Before vs. After</h3><p>The difference between the previous implementation of bulk operations and the new, component-based structure is immediately apparent. Whereas previously all elements, buttons and dialogues were anchored directly in the OverviewView, distributed over numerous event handlers and UI areas, the code is now much more modular. The BulkActionsBar is a standalone component that encapsulates the entire UI for bulk actions. As a result, the OverviewView not only had to accommodate less logic but also gained overall clarity.</p><p>The difference is evident in the reduction of responsibilities within the view. Before the refactoring, the OverviewView handled all aspects: providing the buttons, displaying the bar, handling selection, opening and executing dialogues, and providing user feedback. This resulted in an extensive, tightly coupled block of code that was difficult to maintain and error-prone.</p><p>After the refactoring, the structure has improved significantly. All UI-related logic of bulk operations is now exclusively in the BulkActionsBar. The OverviewView is limited to recognising the selection and forwarding the necessary information. The component itself still triggers the actions, but the view is no longer involved in the UI’s visual or structural layout. This change improves readability, as each functional area is now located where it belongs. In addition, the risk of unintended side effects is significantly reduced, as changes to the BulkActionsBar no longer require direct intervention in the OverviewView. To clarify the difference, it is worth reviewing typical places that were previously located directly in the OverviewView and are now outsourced. A classic example is the handling of the bulk deletion operation. In the past, the OverviewView itself handled dialogue, loops, error handling, and refresh. Today, the entire logic can be found clearly closed in the BulkActionsBar:</p><p><strong>New structure – outsourced bulk delete logic:</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">confirmBulkDeleteSelected</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">selected</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getSelectedItems</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">selected</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notifications</span><span class="p">.</span><span class="na">noSelection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Dialog</span><span class="w"/><span class="n">dialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">setHeaderTitle</span><span class="p">(</span><span class="s">"Delete "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">selected</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" short links?"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">exampleCodes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">selected</span><span class="p">.</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">ShortUrlMapping</span><span class="p">::</span><span class="n">shortCode</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">sorted</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">limit</span><span class="p">(</span><span class="n">5</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">toList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">exampleCodes</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">preview</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">String</span><span class="p">.</span><span class="na">join</span><span class="p">(</span><span class="s">", "</span><span class="p">,</span><span class="w"/><span class="n">exampleCodes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">selected</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">5</span><span class="p">)</span><span class="w"/><span class="n">preview</span><span class="w"/><span class="o">+=</span><span class="w"/><span class="s">", ..."</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Text</span><span class="p">(</span><span class="s">"Examples: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">preview</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Text</span><span class="p">(</span><span class="s">"Delete selected short links?"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">confirm</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">success</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">failed</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kd">var</span><span class="w"/><span class="n">m</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">selected</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">ok</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">urlShortenerClient</span><span class="p">.</span><span class="na">delete</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">ok</span><span class="p">)</span><span class="w"/><span class="n">success</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">else</span><span class="w"/><span class="n">failed</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">error</span><span class="p">(</span><span class="s">"Bulk delete failed for {}"</span><span class="p">,</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">(),</span><span class="w"/><span class="n">ex</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">failed</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">deselectAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notifications</span><span class="p">.</span><span class="na">deletedAndNotDeleted</span><span class="p">(</span><span class="n">success</span><span class="p">,</span><span class="w"/><span class="n">failed</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">confirm</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">,</span><span class="w"/><span class="n">LUMO_ERROR</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">cancel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">getFooter</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">confirm</span><span class="p">,</span><span class="w"/><span class="n">cancel</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">open</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here you can see that the entire interaction – from the UI to the error handling to the update – now takes place<em>within the component</em> and no longer burdens the OverviewView.</p><p><strong>New structure – View only signals the selection:</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="n">addSelectionListener</span><span class="p">(</span><span class="n">event</span><span class="o">-&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">all</span><span class="p">=</span><span class="n">event</span><span class="p">.</span><span class="n">getAllSelectedItems</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">boolean</span><span class="n">hasSelection</span><span class="p">=</span><span class="p">!</span><span class="n">all</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">();</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">setVisible</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">int</span><span class="n">count</span><span class="p">=</span><span class="n">all</span><span class="p">.</span><span class="n">size</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">String</span><span class="n">label</span><span class="p">=</span><span class="n">count</span><span class="o">==</span><span class="mi">1</span><span class="p">?</span><span class="s2">"link selected"</span><span class="p">:</span><span class="s2">"links selected"</span><span class="p">;</span></span></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">selectionInfoText</span><span class="p">(</span><span class="n">count</span><span class="p">+</span><span class="s2">" "</span><span class="p">+</span><span class="n">label</span><span class="p">+</span><span class="s2">" on page "</span><span class="p">+</span><span class="n">currentPage</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">selectionInfoText</span><span class="p">(</span><span class="s2">""</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">setButtonsEnabled</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>This shows the new role distribution: the OverviewView only controls visibility and status display. In the past, the entire operation logic would have been implemented here as well. Another example is opening the bulk set expiry dialogues. This logic is now also completely in the component and no longer distributed across the View:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">bulkSetExpiryBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">openBulkSetExpiryDialog</span><span class="p">());</span></span></span></code></pre></div></div><p>The entire dialogue logic is then located in:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">openBulkSetExpiryDialog</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/><span class="p">...</span><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Result:</strong><br>
The clear separation of responsibilities results in:</p><ul><li>significantly fewer lines of code in the OverviewView,</li><li>a more structured, modular architecture,</li><li>less coupling,</li><li>improved maintainability and expandability.</li></ul><p>Overall, this results in a more maintenance-friendly, extensible and clear structured architecture.</p><h2 id="search-requirements">Search requirements</h2><p>A practical search function is a central part of any administrative interface, especially when the number of entries is continuously growing. In the context of URL shorteners, this means that users can quickly and specifically access specific short links – regardless of whether they are looking for a particular shortcode, a URL fragment, the active status or time criteria. The previous implementation offered only simple filter options and was tightly coupled to the OverviewView, making both extensibility and maintenance difficult.</p><p>However, the requirements for a modern search component go far beyond a simple text field. It must support multiple filter criteria, respond flexibly to new fields, and adapt dynamically to the backend data structure. At the same time, it should offer the user an intuitive, consistent and clutter-free interface. This includes clear input fields, understandable labels, sensible default values, and the ability to change or reset search parameters quickly.</p><p>Technical reliability is just as crucial as user-friendliness: the search function must not burden the backend unnecessarily, support high-performance queries, and use server-side filters efficiently. A clean separation between UI, filter logic and data retrieval not only enables a better overview, but also later extensions – such as additional filter fields, sorting options or advanced functions such as combining several criteria.</p><p>The introduction of the new SearchBar addresses these requirements. It serves as the comprehensive filter and control centre for the overview and ensures that the view itself is decoupled from the filter logic. In doing so, it lays the foundation for a scalable and user-friendly search and filtering experience.</p><h3 id="structure-and-internal-logic-of-the-searchbar">Structure and internal logic of the SearchBar</h3><p>The SearchBar is designed as an independent UI component that aggregates all filter and search parameters from the OverviewView and presents them in a clearly structured format. This component aims to centralise previously scattered filter logic and significantly improve view clarity. Whereas previously individual input fields and sorting parameters were defined directly in the OverviewView, the SearchBar now assumes full responsibility for their management.</p><p>Its structure follows a modular concept: Each input – be it a text filter, a sorting criterion, the number of displayed elements or filtering by properties such as active status or expiration date – is clearly demarcated within the component and is processed independently. This not only ensures better logical separation but also enables flexible expansion with additional filters without requiring adjustments to the view itself.</p><p>The internal logic of the SearchBar works closely with the backend. Based on the user-selected parameters, the component creates structured filter objects that can be passed to the server consistently. Instead of collecting individual parameters loosely or combining them in the view, they are merged in a clearly defined process: validated, normalised, and then transferred to a backend request.</p><p>The central method buildFilter, which creates a<code>UrlMappingListRequest</code> object<code>from the UI inputs,</code>shows what this process looks like in concrete terms:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">public</span><span class="n">UrlMappingListRequest</span><span class="n">buildFilter</span><span class="p">(</span><span class="n">Integer</span><span class="n">page</span><span class="p">,</span><span class="n">Integer</span><span class="n">size</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="nc">UrlMappingListRequest</span><span class="p">.</span><span class="n">Builder</span><span class="n">b</span><span class="p">=</span><span class="nc">UrlMappingListRequest</span><span class="p">.</span><span class="n">builder</span><span class="p">();</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">ActiveState</span><span class="n">activeStateValue</span><span class="p">=</span><span class="n">activeState</span><span class="p">.</span><span class="n">getValue</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">().</span><span class="n">info</span><span class="p">(</span><span class="s2">"buildFilter - activeState == {}"</span><span class="p">,</span><span class="n">activeStateValue</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">activeStateValue</span><span class="o">!=</span><span class="k">null</span><span class="o">&amp;&amp;</span><span class="n">activeStateValue</span><span class="p">.</span><span class="n">isSet</span><span class="p">())</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">b</span><span class="p">.</span><span class="n">active</span><span class="p">(</span><span class="n">activeStateValue</span><span class="p">.</span><span class="n">toBoolean</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">codePart</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span><span class="o">!=</span><span class="k">null</span><span class="o">&amp;&amp;</span><span class="p">!</span><span class="n">codePart</span><span class="p">.</span><span class="n">getValue</span><span class="p">().</span><span class="n">isBlank</span><span class="p">())</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">b</span><span class="p">.</span><span class="n">codePart</span><span class="p">(</span><span class="n">codePart</span><span class="p">.</span><span class="n">getValue</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">urlPart</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span><span class="o">!=</span><span class="k">null</span><span class="o">&amp;&amp;</span><span class="p">!</span><span class="n">urlPart</span><span class="p">.</span><span class="n">getValue</span><span class="p">().</span><span class="n">isBlank</span><span class="p">())</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">b</span><span class="p">.</span><span class="n">urlPart</span><span class="p">(</span><span class="n">urlPart</span><span class="p">.</span><span class="n">getValue</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">fromDate</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span><span class="o">!=</span><span class="k">null</span><span class="o">&amp;&amp;</span><span class="n">fromTime</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span><span class="o">!=</span><span class="k">null</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">zdt</span><span class="p">=</span><span class="nc">ZonedDateTime</span><span class="p">.</span><span class="n">of</span><span class="p">(</span><span class="n">fromDate</span><span class="p">.</span><span class="n">getValue</span><span class="p">(),</span><span class="n">fromTime</span><span class="p">.</span><span class="n">getValue</span><span class="p">(),</span><span class="nc">ZoneId</span><span class="p">.</span><span class="n">systemDefault</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="n">b</span><span class="p">.</span><span class="n">from</span><span class="p">(</span><span class="n">zdt</span><span class="p">.</span><span class="n">toInstant</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="k">if</span><span class="p">(</span><span class="n">fromDate</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span><span class="o">!=</span><span class="k">null</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">zdt</span><span class="p">=</span><span class="n">fromDate</span><span class="p">.</span><span class="n">getValue</span><span class="p">().</span><span class="n">atStartOfDay</span><span class="p">(</span><span class="nc">ZoneId</span><span class="p">.</span><span class="n">systemDefault</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="n">b</span><span class="p">.</span><span class="n">from</span><span class="p">(</span><span class="n">zdt</span><span class="p">.</span><span class="n">toInstant</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">toDate</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span><span class="o">!=</span><span class="k">null</span><span class="o">&amp;&amp;</span><span class="n">toTime</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span><span class="o">!=</span><span class="k">null</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">zdt</span><span class="p">=</span><span class="nc">ZonedDateTime</span><span class="p">.</span><span class="n">of</span><span class="p">(</span><span class="n">toDate</span><span class="p">.</span><span class="n">getValue</span><span class="p">(),</span><span class="n">toTime</span><span class="p">.</span><span class="n">getValue</span><span class="p">(),</span><span class="nc">ZoneId</span><span class="p">.</span><span class="n">systemDefault</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="n">b</span><span class="p">.</span><span class="n">to</span><span class="p">(</span><span class="n">zdt</span><span class="p">.</span><span class="n">toInstant</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="k">if</span><span class="p">(</span><span class="n">toDate</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span><span class="o">!=</span><span class="k">null</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">zdt</span><span class="p">=</span><span class="n">toDate</span><span class="p">.</span><span class="n">getValue</span><span class="p">().</span><span class="n">atTime</span><span class="p">(</span><span class="mi">23</span><span class="p">,</span><span class="mi">59</span><span class="p">).</span><span class="n">atZone</span><span class="p">(</span><span class="nc">ZoneId</span><span class="p">.</span><span class="n">systemDefault</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="n">b</span><span class="p">.</span><span class="n">to</span><span class="p">(</span><span class="n">zdt</span><span class="p">.</span><span class="n">toInstant</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">sortBy</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span><span class="o">!=</span><span class="k">null</span><span class="o">&amp;&amp;</span><span class="p">!</span><span class="n">sortBy</span><span class="p">.</span><span class="n">getValue</span><span class="p">().</span><span class="n">isBlank</span><span class="p">())</span><span class="n">b</span><span class="p">.</span><span class="n">sort</span><span class="p">(</span><span class="n">sortBy</span><span class="p">.</span><span class="n">getValue</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">dir</span><span class="p">.</span><span class="n">getValue</span><span class="p">()</span><span class="o">!=</span><span class="k">null</span><span class="o">&amp;&amp;</span><span class="p">!</span><span class="n">dir</span><span class="p">.</span><span class="n">getValue</span><span class="p">().</span><span class="n">isBlank</span><span class="p">())</span><span class="n">b</span><span class="p">.</span><span class="n">dir</span><span class="p">(</span><span class="n">dir</span><span class="p">.</span><span class="n">getValue</span><span class="p">());</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">page</span><span class="o">!=</span><span class="k">null</span><span class="o">&amp;&amp;</span><span class="n">size</span><span class="o">!=</span><span class="k">null</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">b</span><span class="p">.</span><span class="n">page</span><span class="p">(</span><span class="n">page</span><span class="p">).</span><span class="n">size</span><span class="p">(</span><span class="n">size</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">filter</span><span class="p">=</span><span class="n">b</span><span class="p">.</span><span class="n">build</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">().</span><span class="n">info</span><span class="p">(</span><span class="s2">"buildFilter - {}"</span><span class="p">,</span><span class="n">filter</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">return</span><span class="n">filter</span><span class="p">;</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The code shows how all relevant filter fields – active status, shortcode and URL parts, time windows, and sorting and paging information – are consolidated in a single place. The SearchBar thus serves as a translator between UI inputs and the domain-specific filter structure in the backend. This creates a robust interface that both reduces errors and facilitates future expansions. At the same time, the SearchBar ensures a consistent user experience. Changes to a field automatically update the results list, while sensible default values and a consistent display ensure a familiar user experience. This combination of structural clarity, technical precision, and ease of use makes the SearchBar a central building block of the project&rsquo;s modern UI architecture.</p><h3 id="improvements-compared-to-the-previous-solution">Improvements compared to the previous solution</h3><p>From a technical standpoint, the new solution offers greater robustness. The processing is now handled entirely in the SearchBar, using structured data objects that are transmitted directly to the backend. This minimises errors caused by inconsistent or incomplete filter parameters. At the same time, the clear structure makes it easy to add new filters without jeopardising existing functionality.</p><p>A key example of the improvements is how changes to the SearchBar filter are handled. Whereas previously the OverviewView had to check and react to each value, the SearchBar now handles this task independently. This is already evident in the ValueChange listeners of the individual input fields:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">safeRefresh</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">safeRefresh</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">activeState</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">setCurrentPage</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">pageSize</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">setCurrentPage</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">setGridPageSize</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>These listeners make it clear that the SearchBar takes complete control of the filters&rsquo; behaviour and automatically updates the view. Previously, this logic was distributed in the OverviewView. Global search has also been significantly improved. It used to be a standalone input field with no real integration, but now it dynamically controls the specific search fields and ensures consistent filters:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">globalSearch</span><span class="p">.</span><span class="n">addValueChangeListener</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">v</span><span class="p">=</span><span class="nc">Optional</span><span class="p">.</span><span class="n">ofNullable</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">getValue</span><span class="p">()).</span><span class="n">orElse</span><span class="p">(</span><span class="s2">""</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">searchScope</span><span class="p">.</span><span class="n">getValue</span><span class="p">().</span><span class="n">equals</span><span class="p">(</span><span class="s2">"Shortcode"</span><span class="p">))</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="n">v</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="n">v</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>As a result, global search has become a real entry point for filter logic, rather than being an additional field with no straightforward integration.</p><p>Another big step forward is the introduction of the reset mechanism, which resets all filters in a targeted manner while ensuring that the UI returns to a consistent state:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">resetBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="kd">var</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">withRefreshGuard</span><span class="p">(</span><span class="kc">true</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">resetElements</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">holdingComponent</span><span class="p">.</span><span class="na">setCurrentPage</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RuntimeException</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Finally, switching between simple and advanced search also shows the new structural quality of the SearchBar:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">advanced</span><span class="p">.</span><span class="na">addOpenedChangeListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">nowClosed</span><span class="w"/><span class="o">=</span><span class="w"/><span class="o">!</span><span class="n">ev</span><span class="p">.</span><span class="na">isOpened</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">nowClosed</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">applyAdvancedToSimpleAndReset</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setSimpleSearchEnabled</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Overall, the new SearchBar is far superior in both functionality and architecture. Their listeners, control logic, and consistent handling of filter elements form the basis for a modern, scalable, and maintenance-friendly search architecture. It is not only functionally better, but also forms the basis for a modern, scalable and maintenance-friendly search architecture.</p><h3 id="interaction-of-the-searchbar-with-grid-and-backend">Interaction of the SearchBar with grid and backend</h3><p>The SearchBar only shows its full strength when interacting with the grid and the backend that powers it. While it serves as a central input component for all search and filter parameters on the interface, it technically forms the link between user interaction and server-side data supply. It is precisely in this role that it becomes clear how crucial the decoupling of the filter logic from the actual view is for performance, maintainability and extensibility.</p><p>In the first step, the SearchBar accepts all user input – from global search texts to specific shortcode or URL parts to date ranges, sort fields and the active status. These values are no longer processed ad hoc in the OverviewView, but collected, validated and harmonised in a structured form within the SearchBar. This keeps filter states consistent and traceable, even when multiple input fields are changed simultaneously.</p><p>The second step is the interaction with the grid. As soon as a relevant filter value changes, the SearchBar notifies the OverviewView of the updated state. This, in turn, triggers a grid update without requiring the individual filter criteria to be known or processed. The grid then calls the backend via its DataProvider and receives a filtered subset of the data based on the SearchBar&rsquo;s requirements. This creates a clearly separated yet closely interlinked system of input, data retrieval and presentation.</p><p>On the backend, the advantage of a structured filter is particularly evident in how the OverviewView configures its DataProvider. The SearchBar always provides a consistent filter object that is passed directly to the backend calls. Central to this is the initialisation of the DataProvider in the OverviewView:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private void initDataProvider() {</span></span><span class="line"><span class="cl"> dataProvider = new CallbackDataProvider<span class="err">&lt;</span>&gt;(</span></span><span class="line"><span class="cl"> q -&gt; {</span></span><span class="line"><span class="cl"> final int uiSize = Optional.ofNullable(searchBar.getPageSize()).orElse(25);</span></span><span class="line"><span class="cl"> final int pageStart = (currentPage - 1) * uiSize;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> final int vLimit = q.getLimit();</span></span><span class="line"><span class="cl"> final int vOffset = q.getOffset();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> final int effectiveLimit = (vLimit &gt; 0) ? vLimit: uiSize;</span></span><span class="line"><span class="cl"> final int effectiveOffset = pageStart + vOffset;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> final int page = (effectiveLimit &gt; 0) ? (effectiveOffset / effectiveLimit) + 1 : 1;</span></span><span class="line"><span class="cl"> final int size = (effectiveLimit &gt; 0) ? effectiveLimit: uiSize;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> final UrlMappingListRequest req = searchBar.buildFilter(page, size);</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> final List<span class="nt">&lt;ShortUrlMapping&gt;</span> items = urlShortenerClient.list(req);</span></span><span class="line"><span class="cl"> return items.stream();</span></span><span class="line"><span class="cl"> } catch (IOException ex) {</span></span><span class="line"><span class="cl"> logger().error("Error fetching (page={}, size={})", page, size, ex);</span></span><span class="line"><span class="cl"> Notifications.loadingFailed();</span></span><span class="line"><span class="cl"> return Stream.empty();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> },</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> _ -&gt; {</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> final UrlMappingListRequest base = searchBar.buildFilter(null, null);</span></span><span class="line"><span class="cl"> totalCount = urlShortenerClient.listCount(base);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> final int uiSize = Optional.ofNullable(searchBar.getPageSize()).orElse(25);</span></span><span class="line"><span class="cl"> final int pageStart = (currentPage - 1) * uiSize;</span></span><span class="line"><span class="cl"> final int remaining = Math.max(0, totalCount - pageStart);</span></span><span class="line"><span class="cl"> final int pageCount = Math.min(uiSize, remaining);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> refreshPageInfo();</span></span><span class="line"><span class="cl"> return pageCount;</span></span><span class="line"><span class="cl"> } catch (IOException ex) {</span></span><span class="line"><span class="cl"> logger().error("Error counting", ex);</span></span><span class="line"><span class="cl"> totalCount = 0;</span></span><span class="line"><span class="cl"> refreshPageInfo();</span></span><span class="line"><span class="cl"> return 0;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> grid.setPageSize(Optional.ofNullable(searchBar.getPageSize()).orElse(25));</span></span><span class="line"><span class="cl"> grid.setDataProvider(dataProvider);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This makes it clear how closely the grid, SearchBar, and backend interact: the DataProvider calculates the effective query parameters from the current paging information and page size, then builds a<code>UrlMappingListRequest</code> from the SearchBar and passes it directly to the<code>URLShortenerClient</code>. The SearchBar is also used for the counting function, this time without paging parameters, to determine the total number of entries.</p><p>The advantage of this structure is that the OverviewView itself does not need to know the details of the filter fields. It delegates the entire filter configuration to the SearchBar and focuses solely on controlling the grid and paging. Changes to the filters – such as additional criteria or changed default values – can be made entirely in the SearchBar without having to adjust the DataProvider or the View.</p><p>Overall, the interaction among SearchBar, Grid, and the backend shows a cleanly orchestrated data-flow model: Users change a filter, the SearchBar generates a unique search query, the DataProvider requests the appropriate data via the<code>URLShortenerClient</code>, and the Grid presents the results. This consistent, clearly structured process makes the entire interface much more stable, understandable and responsive.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Part1-02.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Part1-02.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Part1-02.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - De-/Activate Mappings - Part 2</title><link>https://svenruppert.com/posts/advent-calendar-2025-de-activate-mappings-part-2/</link><pubDate>Sat, 20 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-de-activate-mappings-part-2/</guid><description>What has happened so far? In the first part of this article, the new active/inactive model for shortlinks was introduced and anchored at the architectural level. Based on the technical rationale, it was shown that a pure expiration date is insufficient for modern use cases and that an explicit activity status is required.</description><content:encoded>&lt;![CDATA[<h2 id="what-has-happened-so-far">What has happened so far?</h2><p>In the first part of this article, the new active/inactive model for shortlinks was introduced and anchored at the architectural level. Based on the technical rationale, it was shown that a pure expiration date is insufficient for modern use cases and that an explicit activity status is required.</p><p>Based on this, the technical foundations were laid: the core domain model was extended with an<code>active</code>flag, the DTOs were adapted accordingly, and the serialisation was designed to ensure backward compatibility. In addition, the administrative REST endpoints have been expanded to enable targeted setting, querying, and filtering of activity status. The redirect behaviour has also been clarified so that deactivated and expired shortlinks can be distinguished by clearly defined HTTP status codes.</p><p>The structural basis of the active/inactive model is thus fully established. In the second part, the focus is on practical use: how the Java client maps these new capabilities, how users can conveniently control activity status via the Vaadin interface, and how this results in consistent, efficient workflows in everyday use.</p><p><strong>The source code for this project‘s status can be found on GitHub at the following URL:<a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-10">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-10</a></strong></p><p><figure><img src="/images/2025/12/image-65-1024x453.png" alt="A user interface for a URL shortener application showing an overview of shortlinks with columns for shortcode, URL, creation date, active status, expiration date, and actions including links to activate or deactivate." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-66-1024x455.png" alt="Overview of the URL Shortener application displaying shortlinks with columns for shortcode, URL, creation date, activity status, expiration, and actions." loading="lazy" decoding="async"/></p><h2 id="java-client-enhancements">Java Client Enhancements</h2><p>After the server API had been extended to include functions for activating and deactivating shortlinks, the Java client also had to be adapted accordingly. After all, it is the primary interface for many users to work programmatically with the URL shortener – whether in the context of desktop tools, automations, CI/CD pipelines or embedded systems.</p><p>Chapter 5 details how new capabilities have been added to the client to support the active/inactive model fully. These include:</p><ul><li>the targeted switching of the activity status,</li><li>the initial setting of the activity and expiration date when creating a shortlink,</li><li>as well as editing existing mappings, including the new activity field.</li></ul><p>The extensions are based on a consistent design approach: simple parameters, clear method focus, strict validation, and traceable error handling. For users, this creates an API that is not only complete, but also intuitive to use – regardless of whether individual values are changed or complete mappings are rebuilt.</p><h3 id="new-api-toggleactiveshortcode-active">New API:<code>toggleActive(shortCode, active)</code></h3><p>To allow the user to control the activity status of a shortlink not only via the REST API, but also conveniently via the Java client, a new method has been added to the client API. This method is the functional equivalent of the toggle endpoint on the server side and allows shortlinks to be enabled or disabled directly from applications, scripts, or automations.</p><p>The new API method<code>toggleActive(shortCode, active)</code> does exactly this. It ensures that all relevant information is transmitted to the server in the correct structure and that the server&rsquo;s response is converted into a suitable representation. With a clear focus on switching activity status, the user eliminates the need to build full update objects or send unnecessary data.</p><p>Another advantage of this method is its simplicity: the user only needs to specify the shortcode of the link to be changed as well as the desired new status. The client&rsquo;s internal logic takes care of everything else – from creating the appropriate request payload to interpreting the server response. This makes it particularly intuitive to use and reduces potential sources of error.</p><p>In the next step, we will look at the concrete implementation of this method using the source code. The following implementation comes directly from the<code>URLShortenerClient</code>:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">toggleActive</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">active</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Toggle Active shortCode='{}' active='{}'"</span><span class="p">,</span><span class="w"/><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">active</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">shortCode</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">shortCode</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"shortCode must not be null/blank"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">URI</span><span class="w"/><span class="n">uri</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serverBaseAdmin</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">PATH_ADMIN_TOGGLE_ACTIVE</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">shortCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">URL</span><span class="w"/><span class="n">url</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">uri</span><span class="p">.</span><span class="na">toURL</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Toggle Active - {}"</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">HttpURLConnection</span><span class="w"/><span class="n">con</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">HttpURLConnection</span><span class="p">)</span><span class="w"/><span class="n">url</span><span class="p">.</span><span class="na">openConnection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setRequestMethod</span><span class="p">(</span><span class="s">"PUT"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setDoOutput</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setRequestProperty</span><span class="p">(</span><span class="n">CONTENT_TYPE</span><span class="p">,</span><span class="w"/><span class="n">JSON_CONTENT_TYPE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setRequestProperty</span><span class="p">(</span><span class="n">ACCEPT</span><span class="p">,</span><span class="w"/><span class="n">APPLICATION_JSON</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setConnectTimeout</span><span class="p">(</span><span class="n">CONNECT_TIMEOUT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setReadTimeout</span><span class="p">(</span><span class="n">READ_TIMEOUT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">req</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ToggleActiveRequest</span><span class="p">(</span><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">active</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">toJson</span><span class="p">(</span><span class="n">req</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Toggle Active - request body - '{}'"</span><span class="p">,</span><span class="w"/><span class="n">body</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">getOutputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">body</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(</span><span class="n">UTF_8</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">getResponseCode</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Toggle Active - responseCode {}"</span><span class="p">,</span><span class="w"/><span class="n">code</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">code</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">200</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">204</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">201</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">drainQuietly</span><span class="p">(</span><span class="n">con</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">code</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">404</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"shortCode not found.. {}"</span><span class="p">,</span><span class="w"/><span class="n">shortCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">drainQuietly</span><span class="p">(</span><span class="n">con</span><span class="p">.</span><span class="na">getErrorStream</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">err</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">readAllAsString</span><span class="p">(</span><span class="n">con</span><span class="p">.</span><span class="na">getErrorStream</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Unexpected response: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">", body="</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">err</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The method starts with basic validation and logging. The<code>shortCode</code> parameter must be set because it uniquely identifies the shortlink to change. If the value is null or empty, the client throws an<code>IllegalArgumentException</code> even before an HTTP call is made.</p><p>The next step is to create the destination URL for the API call. The server&rsquo;s administrative base path (<code>serverBaseAdmin</code>) is combined with the toggle endpoint path segment. Thanks to this dynamic composition, the client remains flexible in different deployment environments.</p><p>The client then opens an HTTP connection and configures it for a PUT request. The method sets the expected header fields, including<code>Content-Type</code> (for JSON) and<code>Accept</code>, to define the expected response type.<code>setDoOutput(true)</code> indicates that a request body is included in the request.</p><p>For the actual payload, an instance of<code>ToggleActiveRequest</code> is created, which<code>consists of a shortCode</code> and the desired new<code>active</code> state. This structure is serialized using<code>toJson</code> and then written to the output stream of the connection.</p><p>After the request is sent, the method reads the HTTP status code via<code>con.getResponseCode().</code> The implementation distinguishes between three main cases:</p><ol><li><strong>Successful state change (200, 204, or 201):</strong> The method flushes the InputStream via<code>drainQuietly</code> and returns<code>true</code>. This signals to the user that the shortlink has been updated successfully.</li><li><strong>Shortlink not found (404):</strong> Again, the ErrorStream is emptied. However, the method returns<code>false</code> to make it clear to the user that the shortlink does not exist and therefore cannot be updated.</li><li><strong>All other error cases</strong> : In the event of unexpected or erroneous responses, the ErrorStream is read and packaged together with the HTTP status code in an<code>IOException</code>. This forces the calling code to handle unforeseen errors and prevents such states from being silently ignored.</li></ol><p>Thus,<code>toggleActive</code> provides a clearly defined, robust API for switching activity status via the Java client. It follows the same design principles as the client&rsquo;s other methods: clear validation, consistent error handling, meaningful logging, and lean, JSON-based communication. The implementation thus integrates seamlessly into the existing client architecture and provides a simple yet effective extension of functionality.</p><h3 id="advanced-createcustommapping">Advanced<code>createCustomMapping(...)</code></h3><p>In addition to the possibility to activate or deactivate existing shortlinks afterwards, the process for creating new shortlinks has also been expanded. To allow users to determine whether a shortlink is active when making it, the<code>createCustomMapping(...)</code> method is used in the Java client.</p><p>Before this adjustment, a shortlink could be created using only its fundamental properties – shortcode, original URL, and optional expiration date. The activity status was set implicitly or could only be controlled via later processing steps. With the new extension, the user can determine during creation whether a shortlink is active or inactive.</p><p>The method follows the same principles as the client&rsquo;s other functions: it focuses on clear, simple, and reliable communication with the server. The user only provides the required input values. At the same time, the client takes care of the entire technical processing of the request – from creating the request object to JSON serialisation and interpreting the server response.</p><p>This expansion allows new use cases to be realised. For example, a shortlink can already be created, but only activated later – for example, synchronously with a release time, a marketing campaign or automatically in CI/CD pipelines. At the same time, the uniform data structure ensures that the activity status of a shortlink is treated consistently both when creating and editing.</p><p>In the next step, we examine the concrete implementation of this method using the relevant source code sections and analyse how the extended parameters are integrated into the creation process.</p><p>The extension appears in the<code>URLShortenerClient</code> in the form of two overloaded methods: a simple variant and an extended version with expiration and activity parameters:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="w"/><span class="nf">createCustomMapping</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Create custom mapping alias='{}' url='{}'"</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">createCustomMapping</span><span class="p">(</span><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="kc">null</span><span class="p">,</span><span class="w"/><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="w"/><span class="nf">createCustomMapping</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiredAtOrNull</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="w"/><span class="n">activeOrNull</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Create custom mapping alias='{}' url='{}' expiredAt='{}' active='{}'"</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">expiredAtOrNull</span><span class="p">,</span><span class="w"/><span class="n">activeOrNull</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UrlValidator</span><span class="p">.</span><span class="na">validate</span><span class="p">(</span><span class="n">url</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">result</span><span class="p">.</span><span class="na">valid</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Invalid URL: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">message</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">alias</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">alias</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">validate</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">AliasPolicy</span><span class="p">.</span><span class="na">validate</span><span class="p">(</span><span class="n">alias</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">validate</span><span class="p">.</span><span class="na">valid</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">reason</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">validate</span><span class="p">.</span><span class="na">reason</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="n">reason</span><span class="p">.</span><span class="na">defaultMessage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">shortenRequest</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ShortenRequest</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">expiredAtOrNull</span><span class="p">,</span><span class="w"/><span class="n">activeOrNull</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">toJson</span><span class="p">(</span><span class="n">shortenRequest</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"createCustomMapping - body - '{}'"</span><span class="p">,</span><span class="w"/><span class="n">body</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">URL</span><span class="w"/><span class="n">shortenUrl</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serverBaseAdmin</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">PATH_ADMIN_SHORTEN</span><span class="p">).</span><span class="na">toURL</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"connecting to .. shortenUrl {} (custom)"</span><span class="p">,</span><span class="w"/><span class="n">shortenUrl</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">HttpURLConnection</span><span class="w"/><span class="n">connection</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">HttpURLConnection</span><span class="p">)</span><span class="w"/><span class="n">shortenUrl</span><span class="p">.</span><span class="na">openConnection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">setRequestMethod</span><span class="p">(</span><span class="s">"POST"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">setDoOutput</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">setRequestProperty</span><span class="p">(</span><span class="n">CONTENT_TYPE</span><span class="p">,</span><span class="w"/><span class="n">JSON_CONTENT_TYPE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getOutputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">body</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(</span><span class="n">UTF_8</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">status</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getResponseCode</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Response Code from Server - {}"</span><span class="p">,</span><span class="w"/><span class="n">status</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">200</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">201</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">is</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">jsonResponse</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">String</span><span class="p">(</span><span class="n">is</span><span class="p">.</span><span class="na">readAllBytes</span><span class="p">(),</span><span class="w"/><span class="n">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"createCustomMapping - jsonResponse - {}"</span><span class="p">,</span><span class="w"/><span class="n">jsonResponse</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">shortUrlMapping</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fromJson</span><span class="p">(</span><span class="n">jsonResponse</span><span class="p">,</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"shortUrlMapping .. {}"</span><span class="p">,</span><span class="w"/><span class="n">shortUrlMapping</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">shortUrlMapping</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">409</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">err</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">readAllAsString</span><span class="p">(</span><span class="n">connection</span><span class="p">.</span><span class="na">getErrorStream</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Alias already in use: '"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">alias</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"'. "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">err</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">400</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">err</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">readAllAsString</span><span class="p">(</span><span class="n">connection</span><span class="p">.</span><span class="na">getErrorStream</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Bad request: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">err</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Server returned status "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">status</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The simple variant of<code>createCustomMapping</code> serves as a convenience method: it accepts only an alias and a URL and delegates to the extended version, setting expiration date and activity status to zero. This keeps the API lean for simple use cases, while allowing complete control over the shortlink via the overloaded method.</p><p>The extended method first assumes responsibility for validating the input data.<code>UrlValidator.validate(url)</code> checks whether the specified destination URL meets the expected criteria. If this is not the case, an<code>IllegalArgumentException</code> is thrown with a comprehensible error message. If an alias is set, the alias policy is then checked. This ensures that custom shortcodes comply with the set rules and, for example, do not contain unwanted special characters or prohibited patterns.</p><p>If the URL and alias are valid, a<code>ShortenRequest is created that includes</code>expiredAtOrNull<code>and</code>activeOrNull,<code> in addition to URL and alias</code>. In this way, the user can control whether the shortlink has an expiration date and whether it should be initially active or inactive when creating it. The request is then serialised in JSON format and sent to the<code>PATH_ADMIN_SHORTEN</code> endpoint.</p><p>The HTTP configuration follows the familiar pattern: A<code>POST</code>request is created with a JSON body, the<code>Content-Type</code> is set accordingly, and the body is transmitted via the OutputStream. The server&rsquo;s response is first checked against the HTTP status code. If successful (<code>200</code> or<code>201</code>), the client reads the response body, converts it to a<code>ShortUrlMapping</code> object, and returns it to the user.</p><p>Two special paths are provided for error cases: If the alias reservation fails because the alias is already assigned (<code>409 Conflict</code>), an<code>IllegalArgumentException</code> is thrown with a clear description. General validation errors (<code>400 Bad Request</code>) also throw a meaningful<code>IllegalArgumentException</code>. All other unexpected status codes result in an<code>IOException</code> that includes the exact status code and error message from the server.</p><p>Overall, the advanced<code>createCustomMapping</code> method fits seamlessly into the existing API design. It allows users to create shortlinks in one step with an alias, expiration date, and activity status, combining strict validation with precise, predictable error handling.</p><h3 id="adjustments-to-edit--processing-with-activity-status">Adjustments to<code>edit(...)</code> – Processing with activity status</h3><p>In addition to creating new shortlinks, users often need to customise existing listings. This may involve updating the target URL, adjusting an expiration date, or, in the context of the new functionality, activating or deactivating an existing shortlink.</p><p>To cover these requirements, the existing<code>edit(...)</code>method of the Java client has been extended. It now also supports the optional activity status parameter, so the user no longer needs to change this value via a downstream toggle API; they can control it directly during editing.</p><p>This approach facilitates workflows in which multiple properties of a shortlink are edited simultaneously. Instead of making various API calls in sequence, the entire change process can be combined into a single edit operation. This means that the API remains efficient, consistent and can be easily integrated into various application scenarios – from manual editing to the UI to automated processes in scripts or backend systems.</p><p>In the next step, we will discuss the implementation of the extended<code>edit(...)</code>method.</p><p>The following implementation comes from the<code>URLShortenerClient</code> and shows how the activity state was incorporated into the editing process:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">edit</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">newUrl</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiresAtOrNull</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="w"/><span class="n">activeOrNull</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Edit mapping alias='{}' url='{}' expiredAt='{}' active='{}'"</span><span class="p">,</span><span class="w"/><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">newUrl</span><span class="p">,</span><span class="w"/><span class="n">expiresAtOrNull</span><span class="p">,</span><span class="w"/><span class="n">activeOrNull</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">shortCode</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">shortCode</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"shortCode must not be null/blank"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">newUrl</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">newUrl</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"newUrl must not be null/blank"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">URI</span><span class="w"/><span class="n">uri</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serverBaseAdmin</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">PATH_ADMIN_EDIT</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">shortCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">URL</span><span class="w"/><span class="n">url</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">uri</span><span class="p">.</span><span class="na">toURL</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"edit - {}"</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">HttpURLConnection</span><span class="w"/><span class="n">con</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">HttpURLConnection</span><span class="p">)</span><span class="w"/><span class="n">url</span><span class="p">.</span><span class="na">openConnection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setRequestMethod</span><span class="p">(</span><span class="s">"PUT"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setDoOutput</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setRequestProperty</span><span class="p">(</span><span class="n">CONTENT_TYPE</span><span class="p">,</span><span class="w"/><span class="n">JSON_CONTENT_TYPE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setRequestProperty</span><span class="p">(</span><span class="n">ACCEPT</span><span class="p">,</span><span class="w"/><span class="n">APPLICATION_JSON</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setConnectTimeout</span><span class="p">(</span><span class="n">CONNECT_TIMEOUT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">setReadTimeout</span><span class="p">(</span><span class="n">READ_TIMEOUT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">ShortenRequest</span><span class="w"/><span class="n">req</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ShortenRequest</span><span class="p">(</span><span class="n">newUrl</span><span class="p">,</span><span class="w"/><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">expiresAtOrNull</span><span class="p">,</span><span class="w"/><span class="n">activeOrNull</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">toJson</span><span class="p">(</span><span class="n">req</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"edit - request body - '{}'"</span><span class="p">,</span><span class="w"/><span class="n">body</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">getOutputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">body</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(</span><span class="n">UTF_8</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">con</span><span class="p">.</span><span class="na">getResponseCode</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"edit - responseCode {}"</span><span class="p">,</span><span class="w"/><span class="n">code</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">code</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">200</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">204</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">201</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">drainQuietly</span><span class="p">(</span><span class="n">con</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">code</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">404</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"shortCode not found.. {}"</span><span class="p">,</span><span class="w"/><span class="n">shortCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">drainQuietly</span><span class="p">(</span><span class="n">con</span><span class="p">.</span><span class="na">getErrorStream</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">err</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">readAllAsString</span><span class="p">(</span><span class="n">con</span><span class="p">.</span><span class="na">getErrorStream</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Unexpected response: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">", body="</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">err</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The method accepts four parameters: the shortcode to change, the new destination URL, an optional expiration date, and an optional activity status. Right from the start, we check whether the necessary fields – especially<code>shortCode</code> and<code>newUrl</code> – are valid. An IllegalArgumentException immediately catches invalid inputs.</p><p>The request is then sent as a<code>PUT</code> to the corresponding edit endpoint. The payload consists of a<code>ShortenRequest</code>that contains all editable attributes of a shortlink – including the activity specification<code>activeOrNull</code>. This allows the user to update the URL, expiration date, and activity status in a single process.</p><p>The client then processes the HTTP response. Success cases (<code>200</code>,<code>201</code>,<code>204</code>) return<code>true</code>, while a<code>404</code> clearly indicates that the shortlink does not exist. Unexpected status codes cause an<code>IOException</code>, which allows the user to provide precise fault diagnoses.</p><p>This extension makes the<code>edit(...)</code>method fully capable of updating all relevant properties of a shortlink in a single step, including the activity state, which was previously only controllable via a separate toggle endpoint.</p><h2 id="user-interactions-and-ui-logic-in-the-vaadin-interface">User interactions and UI logic in the Vaadin interface</h2><p>After the previous chapters covered server-side REST handlers, client APIs, and persistence, this chapter focuses on the application&rsquo;s UI layer. The<em>OverviewView</em> is the central administration tool for users to search, filter, edit, and manage shortlinks in bulk.</p><p>Chapter 6, therefore, sheds light on the most critical user interactions in the frontend, in particular:</p><ul><li>The interaction of<strong>Vaadin-Grid</strong> ,<strong>CallbackDataProvider</strong> and dynamic filter parameters</li><li>the integration of<strong>single actions</strong> (e.g. activating/deactivating a link by clicking on an icon)</li><li>Implement more complex<strong>bulk operations</strong> , including dialogues, error handling, and visual feedback</li><li>The connection between UI inputs and the REST endpoints via the<code>URLShortenerClient</code></li></ul><h3 id="switching-the-active-state-directly-in-the-grid">Switching the active state directly in the grid</h3><p>To allow users to change the active or inactive status of a shortlink not only via the API, but also conveniently in the user interface, the grid of the administration view has been extended. The goal is to create the most direct, intuitive, and low-risk way possible to switch the status of a shortlink without opening separate dialogues or editing masks.</p><p>At the centre of each table row is a clearly recognisable visual button. With a single click, the user can toggle a shortlink&rsquo;s status between active and inactive. The interface immediately signals the current state of the shortlink and the action that will be taken when clicked.</p><p>This enlargement has three main objectives:</p><ul><li><strong>Speed</strong> – frequent switching does not require navigation into additional masks.</li><li><strong>Transparency</strong> – the user can see at all times which shortlinks are currently active.</li><li><strong>Robustness</strong> – possible error situations are clearly communicated and do not affect the rest of the application.</li></ul><p>The core of the implementation lies in configuring the grid and the new &ldquo;Active&rdquo; column. The relevant snippet from the<code>OverviewView</code> looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="na">addComponentColumn</span><span class="p">(</span><span class="n">m</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Icon</span><span class="w"/><span class="n">icon</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">active</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">?</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CHECK_CIRCLE</span><span class="p">.</span><span class="na">create</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">:</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CLOSE_CIRCLE</span><span class="p">.</span><span class="na">create</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">icon</span><span class="p">.</span><span class="na">setColor</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">active</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"var(--lumo-success-color)"</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"var(--lumo-error-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">icon</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"cursor"</span><span class="p">,</span><span class="w"/><span class="s">"pointer"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">icon</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">active</span><span class="p">()</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"Deactivate"</span><span class="p">:</span><span class="w"/><span class="s">"Activate"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">icon</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">newValue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="o">!</span><span class="n">m</span><span class="p">.</span><span class="na">active</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">urlShortenerClient</span><span class="p">.</span><span class="na">toggleActive</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">(),</span><span class="w"/><span class="n">newValue</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Status updated"</span><span class="p">,</span><span class="w"/><span class="n">2000</span><span class="p">,</span><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">Position</span><span class="p">.</span><span class="na">TOP_CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Error updating active status: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">getMessage</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">3000</span><span class="p">,</span><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">Position</span><span class="p">.</span><span class="na">TOP_CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">icon</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">})</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Active"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setKey</span><span class="p">(</span><span class="s">"active"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setResizable</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setSortable</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setFlexGrow</span><span class="p">(</span><span class="n">0</span><span class="p">);</span></span></span></code></pre></div></div><p>Instead of a plain text field, a<strong>component column is</strong> used here, displaying a separate icon for each row. The choice of icon depends directly on the current activity status of the respective shortlink:</p><ul><li>If<code>m.active() is</code><strong>true</strong> , a<code>CHECK_CIRCLE</code>icon is displayed.</li><li>If<code>m.active()is</code><strong>false</strong> , a<code>CLOSE_CIRCLE</code> icon is used.</li></ul><p>The colour scheme (<code>success</code> for active,<code>error</code> for inactive) allows the user to recognise the state at a glance. In addition, the<code>title</code> attribute on the icon tells you which action is triggered by a click (&ldquo;Activate&rdquo; or &ldquo;Deactivate&rdquo;).</p><p>The toggle mechanism is implemented in the icon’s click listener. When clicked, the desired new status is first calculated:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">boolean</span><span class="w"/><span class="n">newValue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="o">!</span><span class="n">m</span><span class="p">.</span><span class="na">active</span><span class="p">();</span></span></span></code></pre></div></div><p>The Java client is then called, which delegates the state change to the server via the REST API:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">urlShortenerClient</span><span class="p">.</span><span class="na">toggleActive</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">(),</span><span class="w"/><span class="n">newValue</span><span class="p">);</span></span></span></code></pre></div></div><p>If the call succeeds, the user receives a short, unobtrusive confirmation notification, and the grid is reloaded via<code>safeRefresh().</code> This means that subsequent changes (e.g. filtering by active status) are also displayed correctly immediately.</p><p>Error handling follows the same pattern as in the client API: If an exception occurs – whether due to network problems, unexpected HTTP status codes or server errors – it is communicated in the UI via a clearly visible notification:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Error updating active status: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">getMessage</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">3000</span><span class="p">,</span><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">Position</span><span class="p">.</span><span class="na">TOP_CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>For the user, this means that the active status of a shortlink can be changed directly in the overview with a click. The UI combines clear visual cues (icon, colour, tooltip) with immediate feedback and robust error handling. This reduces the need for separate processing dialogues and makes typical administrative tasks around active/inactive much more efficient.</p><h3 id="filter-by-active-and-inactive-status">Filter by active and inactive status</h3><p>In addition to the ability to switch a shortlink&rsquo;s active status directly in the grid, the user interface has been enhanced with a precise filter system. This allows the user to search specifically for active, inactive, or all shortlinks – without manual searches or complex queries.</p><p>The goal is to provide the user with a tool that allows them to control the visibility of entries flexibly. This improves both the clarity of large datasets and the efficiency of routine administrative tasks, such as detecting expired or disabled shortlinks.</p><p>A unified selection element controls the new filter and affects both data retrieval and paging. If the user sets the filter to Active, Inactive, or Not set, the query parameters are updated to load and display only relevant records in the grid.</p><p>Central to this is the<code>Select</code>field for the active status, which is defined in the<code>OverviewView</code>:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private final Select<span class="nt">&lt;ActiveState&gt;</span> activeState = new Select<span class="err">&lt;</span>&gt;();</span></span></code></pre></div></div><p>This UI element is configured in the search bar and displayed alongside other search and paging elements. Initialisation is done in the<code>buildSearchBar()</code> block:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">activeState</span><span class="p">.</span><span class="na">setLabel</span><span class="p">(</span><span class="s">"Active state"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">activeState</span><span class="p">.</span><span class="na">setItems</span><span class="p">(</span><span class="n">ActiveState</span><span class="p">.</span><span class="na">values</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">activeState</span><span class="p">.</span><span class="na">setItemLabelGenerator</span><span class="p">(</span><span class="n">state</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="k">switch</span><span class="w"/><span class="p">(</span><span class="n">state</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">ACTIVE</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"Active"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">INACTIVE</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"Inactive"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">NOT_SET</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"Not set"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">activeState</span><span class="p">.</span><span class="na">setEmptySelectionAllowed</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">activeState</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="n">ActiveState</span><span class="p">.</span><span class="na">NOT_SET</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">HorizontalLayout</span><span class="w"/><span class="n">topBar</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">globalSearch</span><span class="p">,</span><span class="w"/><span class="n">searchScope</span><span class="p">,</span><span class="w"/><span class="n">pageSize</span><span class="p">,</span><span class="w"/><span class="n">activeState</span><span class="p">,</span><span class="w"/><span class="n">resetBtn</span><span class="p">);</span></span></span></code></pre></div></div><p>This gives the user a clearly labeled drop-down selection with three states:</p><ul><li><strong>Active</strong> – only active shortlinks</li><li><strong>Inactive</strong> – only inactive shortlinks</li><li><strong>Not set</strong> – no filtering by active status</li></ul><p>The filter is set to NOT_SET by default , so all shortlinks are displayed first. The ItemLabelGenerator function determines the label displayed in the drop-down for each enum value.</p><p>For the filter to be functionally effective, the view reacts to changes in the selection field. A<code>corresponding listener is registered</code> in the addListeners() block:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">activeState</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>As soon as the user changes the active state, the current page is reset to 1 and the data provider is reloaded. This makes the filter changes directly visible and prevents the user from being on an invalid page (for example, if there are fewer entries due to filtering).</p><p>The actual connection to the REST API is created in the buildFilter<code>(...)</code>method block that creates a<code>UrlMappingListRequest</code> from the UI :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">UrlMappingListRequest</span><span class="w"/><span class="nf">buildFilter</span><span class="p">(</span><span class="n">Integer</span><span class="w"/><span class="n">page</span><span class="p">,</span><span class="w"/><span class="n">Integer</span><span class="w"/><span class="n">size</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UrlMappingListRequest</span><span class="p">.</span><span class="na">Builder</span><span class="w"/><span class="n">b</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UrlMappingListRequest</span><span class="p">.</span><span class="na">builder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ActiveState</span><span class="w"/><span class="n">activeStateValue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">activeState</span><span class="p">.</span><span class="na">getValue</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"buildFilter - activeState == {}"</span><span class="p">,</span><span class="w"/><span class="n">activeStateValue</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">activeStateValue</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">activeStateValue</span><span class="p">.</span><span class="na">isSet</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">active</span><span class="p">(</span><span class="n">activeStateValue</span><span class="p">.</span><span class="na">toBoolean</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// ... Other filters (codePart, urlPart, time periods, sorting, paging)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">page</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">page</span><span class="p">(</span><span class="n">page</span><span class="p">).</span><span class="na">size</span><span class="p">(</span><span class="n">size</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">filter</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"buildFilter - {}"</span><span class="p">,</span><span class="w"/><span class="n">filter</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">filter</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here, the enum value of the<code>activeState</code>select is read and, if it is considered &ldquo;set,&rdquo; converted to a Boolean (true for active, false for inactive). The request builder takes over this value and later transmits it to the server by the client (<code>URLShortenerClient</code>) as a query parameter. If the state is<code>NOT_SET</code>, no<code>active value is set, so</code> there is no active-status restriction on the server side.</p><p>Together, these building blocks form a consistent filter concept:</p><ul><li>The<code>Select&lt;ActiveState&gt;</code>provides a clear, three-level selection.</li><li>The ValueChangeListener ensures that filter changes take effect immediately.</li><li><code>buildFilter(...)</code> translates the UI selection into a typed request to the backend API.</li></ul><p>For users, this creates a seamless interaction: Switching from &ldquo;Active&rdquo; to &ldquo;Inactive&rdquo; immediately leads to only the corresponding shortlinks appearing in the grid – without this logic having to be duplicated in the frontend or filtered on the client side.</p><h3 id="mass-operations-based-on-active-status">Mass operations based on active status.</h3><p>In addition to single editing, the interface offers convenient bulk operations that let users enable or disable multiple shortlinks at once. This is especially helpful in scenarios where large amounts of data need to be managed, such as temporarily shutting down campaign links or reactivating an entire group of previously disabled shortlinks.</p><p>In this extension, bulk handling has been designed to integrate seamlessly with the existing grid and the integrated selection mechanisms. The user can select as many rows as they want in the grid and then trigger appropriate actions via clearly recognisable buttons in the bulk bar at the bottom of the screen.</p><p>The process always follows the same pattern:</p><ol><li>The user marks the desired short links.</li><li>A confirmation dialogue ensures that the mass change is deliberately triggered.</li><li>The action is performed for each selected entry, regardless of potential errors.</li><li>The result is displayed to the user in a compact success/failure overview.</li></ol><p>This approach enables robust yet user-friendly bulk editing. The interface remains responsive, and error handling ensures that a failure on a single shortlink does not affect the entire operation.</p><p>The following snippet is taken directly from the<code>OverviewView</code> and shows the central method that enables or disables a set of shortlinks in one pass:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private void bulkSetActive(Set<span class="nt">&lt;ShortUrlMapping&gt;</span> selected, boolean activate) {</span></span><span class="line"><span class="cl"> int success = 0;</span></span><span class="line"><span class="cl"> int failed = 0;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> for (var m : selected) {</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> var ok = urlShortenerClient.toggleActive(m.shortCode(), activate);</span></span><span class="line"><span class="cl"> if (ok) {</span></span><span class="line"><span class="cl"> success++;</span></span><span class="line"><span class="cl"> } else {</span></span><span class="line"><span class="cl"> failed++;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> } catch (IOException ex) {</span></span><span class="line"><span class="cl"> logger().error("Toggle active state failed for {}", m.shortCode(), ex);</span></span><span class="line"><span class="cl"> failed++;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> grid.deselectAll();</span></span><span class="line"><span class="cl"> safeRefresh();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var actionLabel = activate ? "Activate" : "Deactivate";</span></span><span class="line"><span class="cl"> Notification.show(</span></span><span class="line"><span class="cl"> actionLabel + " – Success: " + success + " • Failed: " + failed</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This method is central to mass activation. It receives the shortlinks currently selected in the grid, as well as the target status (<code>activate = true</code> or<code>false</code>). For each entry, the corresponding request is sent to the server API via the Java client. The approach is deliberately designed to be fault-tolerant: a failure of one entry does not affect the rest of the processing.</p><p>The UI is not updated until the loop is complete. This keeps the operation clear and efficient, even with many shortlinks. The result is displayed to the user in a compact summary.</p><p>The user triggers this operation via a confirmation dialogue. This is implemented in<code>confirmBulkSetActiveSelected(boolean activate</code> ):</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">confirmBulkSetActiveSelected</span><span class="p">(</span><span class="kt">boolean</span><span class="w"/><span class="n">activate</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">selected</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getSelectedItems</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">selected</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"No entries selected"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">verb</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">activate</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"activate"</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"deactivate"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">verbCap</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">activate</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"Activate"</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"Deactivate"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Dialog</span><span class="w"/><span class="n">dialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">setHeaderTitle</span><span class="p">(</span><span class="n">verbCap</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" all "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">selected</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" short links?"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Text</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"This will "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">verb</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" all selected short links. "</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"They will be "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="p">(</span><span class="n">activate</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">" active"</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"inactive"</span><span class="p">)</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" afterwards."</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">cancel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">confirm</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="n">verbCap</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" All"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkSetActive</span><span class="p">(</span><span class="n">Set</span><span class="p">.</span><span class="na">copyOf</span><span class="p">(</span><span class="n">selected</span><span class="p">),</span><span class="w"/><span class="n">activate</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">confirm</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">getFooter</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">cancel</span><span class="p">,</span><span class="w"/><span class="n">confirm</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">open</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This dialogue ensures that mass changes are not accidentally triggered. This is especially relevant for deactivating actions, as a deactivated shortlink no longer redirects.</p><p>The dialogue is activated via the two buttons in the bulk bar:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">bulkActivateBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">confirmBulkActivateSelected</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">bulkDiActivateBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">confirmBulkDeactivateSelected</span><span class="p">());</span></span></span></code></pre></div></div><p>The associated wrapper methods are:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">confirmBulkActivateSelected</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">confirmBulkSetActiveSelected</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">confirmBulkDeactivateSelected</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">confirmBulkSetActiveSelected</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This means that mass editing is fully integrated into the user interface: selection, confirmation, execution and result feedback follow a clear, consistent process model. For users, an intuitive workflow makes administrative tasks much easier.</p><h2 id="redirect-behaviour-for-end-users">Redirect behaviour for end users.</h2><p>With the introduction of the new active/inactive mechanism and improved handling of expiration times (expiresAt), the URL shortener&rsquo;s behaviour when calling a shortlink changes fundamentally. While users previously received only a redirect or a generic error, the system now provides more granular HTTP status codes that enable precise conclusions about a link&rsquo;s status.</p><h3 id="expired-shortlinks-expiresat--410-gone">Expired Shortlinks (expiresAt) → 410 Gone</h3><p>If a shortlink has an expiration date and has passed, the user must not be redirected to the original URL when accessed. Instead, the system must clearly signal that this shortlink exists, but is no longer valid. This is precisely what the HTTP status code<strong>410 Gone</strong> is used for.</p><p>The status code 410 indicates &ldquo;permanently removed&rdquo; and makes it unmistakably clear that the shortlink was previously active but is now deliberately unavailable. Unlike 404 Not Found, which means that a resource does not exist, 410 conveys a clear semantic meaning:<em>this shortlink has expired and will never be valid again.</em></p><p>For the user, this means the call results in clear error behaviour that remains comprehensible. For developers, on the other hand, this mechanism creates transparency, as they can clearly distinguish between three situations:</p><ul><li>A shortlink does not exist → 404 Not Found</li><li>A shortlink exists, but is disabled → 404 Not Found</li><li>A shortlink exists and has expired →<strong>410 Gone</strong></li></ul><p>The consistent use of the 410 status code also enables better monitoring and cleaner automation processes. Systems such as API gateways, SEO tools, crawlers, or CI/CD pipelines can capture the exact behaviour and thus more precisely explain why a redirect does not occur.</p><h3 id="disabled-active--false--404-not-found">Disabled (active = false) → 404 Not Found</h3><p>Another central component of the new active/inactive model is how deactivated shortlinks are handled. While expired links are signalled with the HTTP status code<em>410 Gone</em> , the system deliberately uses a different status code for disabled links:<strong>404 Not Found</strong>.</p><p>This difference is not accidental, but follows clear safety and operational considerations. A deactivated shortlink should behave as if it no longer exists for the end user, although it remains fully present, visible, and manageable internally.</p><p>This creates a behaviour ideal for maintenance phases, temporary blockades, campaign stops, or safety-related shutdowns. Developers and administrators retain complete control over the dataset without inadvertently revealing internal state information.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Part2-04-steamPunk.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Part2-04-steamPunk.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Part2-04-steamPunk.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - De-/Activate Mappings - Part 1</title><link>https://svenruppert.com/posts/advent-calendar-2025-de-activate-mappings-part-1/</link><pubDate>Fri, 19 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-de-activate-mappings-part-1/</guid><description>Why an active/inactive model for shortlinks? For many users – especially those who work in the field of software development – shortlinks are much more than simple URL shorteners. They act as flexible routing mechanisms for campaigns, feature controls, test scenarios, and internal tools. The requirements for transparency, controllability and clean lifecycle management are correspondingly high.</description><content:encoded>&lt;![CDATA[<h2 id="why-an-activeinactive-model-for-shortlinks">Why an active/inactive model for shortlinks?</h2><p>For many users – especially those who work in the field of software development – shortlinks are much more than simple URL shorteners. They act as flexible routing mechanisms for campaigns, feature controls, test scenarios, and internal tools. The requirements for transparency, controllability and clean lifecycle management are correspondingly high.</p><p>The URL shortener receives an active/inactive model that directly supports these claims. A user can now specify, for each shortlink, whether it should be active, temporarily deactivated, or no longer allowed to reach users due to a planned expiration. At the same time, a clear distinction is made between a disabled and an expired shortlink – both in the UI and in the HTTP behaviour.</p><p>For the user, this creates a noticeable gain in control. An entire campaign can be taken offline with a single click, without losing data or having to edit multiple entries manually. The REST API or the Java client can be used to automatically control activity status externally, enabling integrations with existing development and deployment processes.</p><p>This introduction outlines the motivation for the new model. The following chapters detail how the UI, API, data model, and redirect logic have been extended, and how users benefit from the new active/inactive mechanism.</p><p><strong>The source code for this project‘s status can be found on GitHub at the following URL:<a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-10">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-10</a></strong></p><p><figure><img src="/images/2025/12/image-64-1024x453.png" alt="Overview of a URL shortener interface displaying shortcodes, URLs, creation dates, activity status, expiration dates, and action buttons." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-63-1024x455.png" alt="Screenshot of a URL shortener overview interface displaying a table with shortcode, URL, created date, activity status, expiration, and action buttons." loading="lazy" decoding="async"/></p><h2 id="architecture-overview-of-changes">Architecture overview of changes</h2><p>The new active/inactive model does not work in isolation within a single module; it spans multiple layers of the application. This creates consistent behaviour for the user – regardless of whether a shortlink is edited via the UI, automatically managed via the API or accessed directly in the browser. To understand this interaction, it is worth examining the key areas where adjustments have been made.</p><p>The basis is the extended data model, which now includes an additional attribute to store the activity status. This feature serves as the common denominator for all shortlink-related operations. As a result, both the REST endpoints and the internal Java client have been extended to reliably transmit activity status when creating, modifying, or querying a shortlink.</p><p>These structural changes are also reflected in the user interface. The activity status can now be set directly when creating a shortlink, in the overview table, or in the detail dialogue. Actions such as activating or deactivating multiple entries simultaneously use the exact mechanisms as the API, creating a consistent application experience.</p><p>Last but not least, the redirect behaviour has also been adjusted. While expired shortlinks are clearly indicated, deactivated shortlinks are not. In this way, the user can not only better understand why a shortlink is unreachable, but also receive more precise feedback from both client and monitoring perspectives.</p><p>This architectural overview shows that the active/inactive model is not a one-off feature, but a consistent design principle across all layers of the system. In the following chapters, the respective areas are considered in detail and technically explained.</p><h2 id="the-advanced-data-model">The Advanced Data Model</h2><p>Before the active/inactive model becomes visible in the user interface, REST API, or redirect behaviour, the basic structure must be defined in the data model. Chapter 3 focuses on this basis and shows how a shortlink&rsquo;s activity status is anchored in the core of the system.</p><p>The data model is the central place where all relevant information converges into a shortlink. Every user action – be it creating, editing, filtering or forwarding a shortlink – sooner or later falls back on this structure. Therefore, it is crucial to introduce an activity status that is consistently and reliably available.</p><p>This chapter explains how the new<code>active</code> attribute is mapped to domain objects, how it is transported in DTOs, and the serialisation and backwards-compatibility considerations involved. This clean anchoring in the data model makes the active/inactive model a stable part of the entire application, on which all further technical enhancements are based.</p><h3 id="new-active-flag-in-the-core-domain-model">New<code>active</code>flag in the core domain model.</h3><p>The introduction of the active/inactive model begins at the core domain model level. This defines how a shortlink stores its state and what information is transmitted within the application. The new active attribute lets users check at any time whether a shortlink is currently in use or has been deliberately deactivated.</p><p>The central element of this change is the extension of the<code>ShortUrlMapping</code> class. It outlines the essential characteristics of a short link, including the alias, the original destination URL, and the expiration dates. The new attribute enables the unique determination of the activity status within this structure.</p><p>In the original implementation, the relevant snippet looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">active</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">active</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">active</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="w"/><span class="nf">withActive</span><span class="p">(</span><span class="kt">boolean</span><span class="w"/><span class="n">active</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">shortCode</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">originalUrl</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">createdAt</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">expiresAt</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">active</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This enhancement ensures that activity status is reliably available both in memory and in all downstream layers of the system. The model remains unchanged. The status is not changed in an existing object; instead, it<code/>is created as a new instance using the withActive method. This prevents unintended side effects and supports consistent, deterministic data behaviour.</p><p>This clear separation between active and inactive states provides a stable foundation for UI, server, and redirect logic. In the following subchapters, we will discuss how this information is mapped to the DTOs and the consequences for serialisation and transport behaviour.</p><h3 id="impact-on-dtos-shortenrequest-shorturlmapping">Impact on DTOs (<code>ShortenRequest</code>,<code>ShortUrlMapping</code>)</h3><p>The active/inactive model not only affects the domain core but must also be transported cleanly across the application boundary. Data Transfer Objects (DTOs) are crucial for this. They serve as the interface between the user interface, REST API, and Java client, ensuring that the activity status of a shortlink is transmitted correctly.</p><p>With the expansion of the data model, the DTO<code>ShortUrlMapping</code> also receives an additional attribute that reflects the status of a shortlink. This value is used both in communication from the server to the UI and between the server and the client. A relevant excerpt from the original implementation looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">record</span><span class="nc">ShortUrlMapping</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">shortCode</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">originalUrl</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">createdAt</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiresAt</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">active</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"/><span class="p">{}</span></span></span></code></pre></div></div><p>This extended DTO enables the user to view activity status in the UI and allows external systems to read the status and respond accordingly. By using a record, structure and serialisation remain lean and clearly defined.</p><p>The request to create or change a shortlink has also been adjusted. The<code>ShortenRequest</code> now also includes information on whether a shortlink should be made directly active or initially deactivated. This allows the user to control how the shortlink behaves during creation.</p><p>An excerpt from the corresponding record:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">record</span><span class="nc">ShortenRequest</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">shortCode</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">originalUrl</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiresAt</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Boolean</span><span class="w"/><span class="n">active</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"/><span class="p">{}</span></span></span></code></pre></div></div><p>At this point, it is evident that the request uses a<code>Boolean</code>, whereas the mapping itself uses a primitive<code>Boolean</code>. This difference is deliberate: the value in the request may also be<code>null</code> if the user does not want to set an explicit state. In this case, the server logic decides on a default value. Mapping, on the other hand, always has a defined state, eliminating ambiguity.</p><p>Extending DTOs ensures the active/inactive model is uniformly available across all system boundaries. It forms the basis for the UI, API and client to be informed identically about the status of a shortlink, thus creating a consistent user experience.</p><h3 id="serialisation-and-backward-compatibility">Serialisation and backward compatibility</h3><p>For the active/inactive model to function reliably, the new<code>active</code> value must not only be present in the internal data model but also correctly serialised and transported across different components. The extension of serialisation affects two areas in particular: JSON communication via REST and data exchange between the various application modules.</p><p>By using Java records in the DTOs, serialisation is primarily handled by the chosen JSON library. Once the<code>active</code> attribute is defined in the record constructors, it is automatically included in the JSON representation. This makes integration straightforward and minimises the need for additional configuration.</p><p>An example of the resulting JSON structure of a shortlink as served via the REST API:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">json</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="nt">"shortCode"</span><span class="p">:</span><span class="s2">"abc123"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"originalUrl"</span><span class="p">:</span><span class="s2">"https://example.com"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"createdAt"</span><span class="p">:</span><span class="s2">"2025-05-20T10:15:30Z"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"expiresAt"</span><span class="p">:</span><span class="s2">"2025-06-01T00:00:00Z"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"active"</span><span class="p">:</span><span class="kc">true</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The JSON structure makes it clear that the activity state is immediately visible to the user and can be retrieved without any additional logic. This increase in transparency is central to the new model.</p><p>An essential aspect of this change is backward compatibility. Systems or components that use older versions of the API and are not aware of the<code>active</code> field can continue to interact without issues. JSON consumers typically ignore unknown fields to avoid affecting older clients. At the same time, the server can set a sensible default value for requests that do not specify an active value – typically<code>true</code>.</p><p>This combination of clear serialisation and high backward compatibility ensures that the active/inactive model can be integrated into existing systems without disruption. At the same time, further development remains open to future extensions, such as more differentiated activity states or audit information.</p><h2 id="enhancements-to-the-admin-rest-api">Enhancements to the Admin REST API</h2><p>The active/inactive model only unfolds its full effect through the extensions of the administrative REST API. This establishes the connection between the data model, the internal business logic, and the various clients that create, edit, or automatically manage shortlinks. To enable users to effectively use the new activity state, several API endpoints had to be adapted or supplemented.</p><p>Chapter 4 examines these extensions in detail. It shows how the new toggle endpoint enables targeted switching of activity status, how inactive entries can be queried via a dedicated list endpoint, and how the ActiveState filter extends existing query operations. It also explains how differentiated HTTP status codes clearly convey the semantic meaning of a deactivated or expired shortlink.</p><p>These API customisations create a coherent, well-integrated interface that can map all aspects of the active/inactive model, whether the requests come from a user interface, a Java client, or an automated system.</p><h3 id="new-toggle-endpoint">New Toggle Endpoint</h3><p>To make the active/inactive shortlink model usable across the application, the server component needs a well-defined mechanism to change the activity status of an existing shortlink. For this purpose, a new toggle endpoint was introduced and implemented on the server side by<code>ToggleActiveHandler</code>. It forms the basis for allowing users to adjust the activity state of a shortlink directly from the REST API – whether from the user interface, automations, or external systems.</p><p>The new endpoint has a clear purpose: it enables the targeted switching of the activity status of a specific shortlink. The user can specify, via a simple request, whether a shortlink should be activated or deactivated without changing any other mapping properties. This not only makes the operation more efficient, but also less error-prone, as there is no need for full update payloads.</p><p>This endpoint integrates seamlessly with the existing API structure and uses the same transport models and validation mechanisms as other administrative operations. At the same time, it provides a clear separation between changes of activity status and other editing operations, simplifying both implementation and use.</p><p>The core component of this endpoint is the<code>ToggleActiveHandler</code>. It encapsulates the HTTP-specific processing and delegates the actual state change to the<code>UrlMappingStore</code> behind it:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class ToggleActiveHandler</span></span><span class="line"><span class="cl"> implements HttpHandler, HasLogger {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private final UrlMappingStore store;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public ToggleActiveHandler(UrlMappingStore store) {</span></span><span class="line"><span class="cl"> this.store = store;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void handle(HttpExchange ex)</span></span><span class="line"><span class="cl"> throws IOException {</span></span><span class="line"><span class="cl"> logger().info("handle ... {} ", ex.getRequestMethod());</span></span><span class="line"><span class="cl"> if (! RequestMethodUtils.requirePut(ex)) return;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> final String body = readBody(ex.getRequestBody());</span></span><span class="line"><span class="cl"> ToggleActiveRequest req = fromJson(body, ToggleActiveRequest.class);</span></span><span class="line"><span class="cl"> var shortCode = req.shortCode();</span></span><span class="line"><span class="cl"> if (isNullOrBlank(shortCode)) {</span></span><span class="line"><span class="cl"> writeJson(ex, BAD_REQUEST, "Missing 'shortCode'");</span></span><span class="line"><span class="cl"> return;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> boolean newActiveValue = req.active();</span></span><span class="line"><span class="cl"> logger().info("Toggling active status for {} to {}", shortCode, newActiveValue);</span></span><span class="line"><span class="cl"> Result<span class="nt">&lt;ToggleActiveResponse&gt;</span> mapping = store.toggleActive(shortCode, newActiveValue);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> if (mapping.isPresent()) {</span></span><span class="line"><span class="cl"> logger().info("Toggling active status successfully");</span></span><span class="line"><span class="cl"> writeJson(ex, OK, toJson(mapping.get()));</span></span><span class="line"><span class="cl"> } else {</span></span><span class="line"><span class="cl"> Mapping.</span></span><span class="line"><span class="cl"> ifFailed(failed -&gt; logger().info("Toggling active status failed: {}", failed));</span></span><span class="line"><span class="cl"> writeJson(ex, BAD_REQUEST, "Toggling active status failed");</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> } catch (RuntimeException e) {</span></span><span class="line"><span class="cl"> logger().warn("catch - {}", e.toString());</span></span><span class="line"><span class="cl"> writeJson(ex, INTERNAL_SERVER_ERROR);</span></span><span class="line"><span class="cl"> } finally {</span></span><span class="line"><span class="cl"> logger().info("ToggleActiveHandler .. finally");</span></span><span class="line"><span class="cl"> ex.close();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The handler implements the HttpHandler interface and integrates project-specific logging via<code>HasLogger</code>. In the constructor, the dependency is injected into the<code>UrlMappingStore</code>, which is later used to execute the logic that changes the state.</p><p>The main logic is in the<code>try</code> block. First, the request body is<code>completely read in via</code>readBody(ex.getRequestBody()) and converted<code>to an instance of</code>ToggleActiveRequest using<code>fromJson</code>. This Request object contains the two relevant pieces of information: the<code>shortCode</code> and the new<code>active</code> value. It then checks whether the<code>shortcode</code> is empty or null. In this case, the handler responds immediately with a<code>400 Bad Request</code> and a clear error message, preventing the store layer from encountering invalid data.</p><p>If the<code>shortcode</code> is valid, the desired new activity value is extracted and logged. The actual status change occurs via &ldquo;<code>store.toggleActive(shortCode, newActiveValue)".</code> The result is encapsulated in a<code>Result&lt;ToggleActiveResponse&gt;</code> to ensure both success and failure cases are handled consistently.</p><p>In case of success (<code>mapping.isPresent())</code>, another success entry is written to the log, and the updated display of the shortlink is returned to the user as JSON with the HTTP status<code>200 OK</code>. If the store does not return a result, the cause of the error is logged via<code>ifFailed,</code> and the user is sent a response with a<code>400 Bad Request</code> status code and a generic error message. This clearly defines the error behaviour without revealing too many details internally.</p><p>If unexpected runtime errors occur in the handler itself, the<code>catch</code> block catches the exception, logs a warning, and returns a<code>500 Internal Server Error</code>. The<code>finally</code> block ensures that the connection is closed cleanly via<code>ex.close()</code> in all cases and that a corresponding log entry is created. Overall, this creates a robust, clearly structured endpoint that reliably and comprehensibly updates a shortlink‘s activity status.</p><h3 id="query-inactive-links">Query inactive links</h3><p>In addition to targeting the activity status of an individual shortlink, the user needs the ability to view collected inactive entries. This provides an overview of shortlinks that have been deliberately or automatically disabled and are currently no longer redirecting.</p><p>For this purpose, a special API endpoint was introduced that returns only inactive shortlinks. This endpoint complements the general list endpoint and provides a clear, filtered view of all shortlinks with the &ldquo;inactive&rdquo; status.</p><p>The focus of this endpoint is on visibility and control. Users can quickly see which shortlinks are currently disabled without having to implement their own filtering logic or evaluate the complete list of shortlinks. This is a notable advantage, especially in larger installations or automated workflows, as inactive entries often require separate management processes.</p><p>The endpoint integrates seamlessly with the existing administrative API structure and uses the same data models and return formats as other query operations. It ensures that information about inactive shortlinks is consistently retrievable across all UIs and clients.</p><p>The technical implementation of querying inactive shortlinks is integrated into the existing list handler. The following excerpt shows the<code>ListHandler</code> responsible for this, which provides different list variants, including the inactive entries, via a common endpoint:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class ListHandler</span></span><span class="line"><span class="cl"> implements HttpHandler, HasLogger {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private final UrlMappingLookup store;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public ListHandler(UrlMappingLookup store) {</span></span><span class="line"><span class="cl"> this.store = store;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> SNIPP</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void handle(HttpExchange ex)</span></span><span class="line"><span class="cl"> throws IOException {</span></span><span class="line"><span class="cl"> if (! RequestMethodUtils.requireGet(ex)) return;</span></span><span class="line"><span class="cl"> final String path = ex.getRequestURI().getPath();</span></span><span class="line"><span class="cl"> logger().info("List request: {}", path);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> String responseJson;</span></span><span class="line"><span class="cl"> if (path.endsWith(PATH_ADMIN_LIST_ALL)) {</span></span><span class="line"><span class="cl"> responseJson = listAll();</span></span><span class="line"><span class="cl"> } else if (path.endsWith(PATH_ADMIN_LIST_EXPIRED)) {</span></span><span class="line"><span class="cl"> responseJson = listExpired();</span></span><span class="line"><span class="cl"> } else if (path.endsWith(PATH_ADMIN_LIST_ACTIVE)) {</span></span><span class="line"><span class="cl"> responseJson = listActive();</span></span><span class="line"><span class="cl"> } else if (path.endsWith(PATH_ADMIN_LIST_INACTIVE)) {</span></span><span class="line"><span class="cl"> responseJson = listInActive();</span></span><span class="line"><span class="cl"> } else if (path.endsWith(PATH_ADMIN_LIST)) {</span></span><span class="line"><span class="cl"> responseJson = listFiltered(ex);</span></span><span class="line"><span class="cl"> } else {</span></span><span class="line"><span class="cl"> logger().info("undefined path {}", path);</span></span><span class="line"><span class="cl"> ex.sendResponseHeaders(404, -1);</span></span><span class="line"><span class="cl"> return;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> writeJson(ex, OK, responseJson);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private String listInActive() {</span></span><span class="line"><span class="cl"> final Instant now = Instant.now();</span></span><span class="line"><span class="cl"> return filterAndBuild("inactive",</span></span><span class="line"><span class="cl"> m -&gt; {</span></span><span class="line"><span class="cl"> if (!m.active()) return true;</span></span><span class="line"><span class="cl"> return m.expiresAt()</span></span><span class="line"><span class="cl"> .map(exp -&gt; exp.isBefore(now))</span></span><span class="line"><span class="cl"> .orElse(false);</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private String filterAndBuild(String mode, Predicate<span class="nt">&lt;ShortUrlMapping&gt;</span> predicate) {</span></span><span class="line"><span class="cl"> final Instant now = Instant.now();</span></span><span class="line"><span class="cl"> final var data = store</span></span><span class="line"><span class="cl"> .findAll()</span></span><span class="line"><span class="cl"> .stream()</span></span><span class="line"><span class="cl"> .filter(predicate)</span></span><span class="line"><span class="cl"> .map(m -&gt; toDto(m, now))</span></span><span class="line"><span class="cl"> .toList();</span></span><span class="line"><span class="cl"> return JsonUtils.toJsonListing(mode, data.size(), data);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private Map<span class="nt">&lt;String</span><span class="err">,</span><span class="err">String</span><span class="nt">&gt;</span> toDto(ShortUrlMapping m, Instant now) {</span></span><span class="line"><span class="cl"> boolean expired = m.</span></span><span class="line"><span class="cl"> expiresAt()</span></span><span class="line"><span class="cl"> .map(t -&gt; t.isBefore(now))</span></span><span class="line"><span class="cl"> .orElse(false);</span></span><span class="line"><span class="cl"> return Map.of(</span></span><span class="line"><span class="cl"> "shortCode", m.shortCode(),</span></span><span class="line"><span class="cl"> "originalUrl", m.originalUrl(),</span></span><span class="line"><span class="cl"> "createdAt", m.createdAt().toString(),</span></span><span class="line"><span class="cl"> "expiresAt", m.expiresAt().map(Instant::toString).orElse(""),</span></span><span class="line"><span class="cl"> "active", m.active() + "",</span></span><span class="line"><span class="cl"> "status", expired ? "expired" : "active"</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The<code>ListHandler</code>, like the toggle handler, implements the<code>HttpHandler</code> interface and uses<code>HasLogger</code> for consistent logging. A<code>UrlMappingLookup is</code>injected into the constructor and used for the data queries. The<code>handle</code>method handles routing based on the request path: Depending on whether the request ends in<code>PATH_ADMIN_LIST_ALL</code>,<code>PATH_ADMIN_LIST_ACTIVE,</code> or<code>PATH_ADMIN_LIST_INACTIVE</code>, a specialised list method is called.</p><p>The listInActive()<code>method is responsible for querying inactive shortlinks</code>. It determines the current time and calls<code>filterAndBuild("inactive", ...)</code> with a predicate that selects the desired entries. A shortlink is considered inactive if it is either explicitly marked as inactive (<code>!m.active())</code> or if it is active but has already expired. The combination of the active flag and the expiration date allows a clean separation between currently usable and no longer usable entries.</p><p>The<code>filterAndBuild method reads</code> all entries from the<code>UrlMappingLookup</code>, applies the passed predicate, and converts the remaining mappings into a DTO-like structure. For this purpose,<code>toDto</code> is used to create a map of the most essential attributes from each<code>ShortUrlMapping</code>. In addition to<code>shortCode</code>,<code>originalUrl</code>,<code>createdAt,</code> and<code>expiresAt, the</code>active<code>status and a derived field,</code>status,<code> are set here</code>, which distinguishes between<code>expired</code> and<code>active</code>. This additional information makes it easier for the user to read the entry status directly from the response.</p><p>This structure allows queries for inactive links to fit seamlessly into the existing list concept. Instead of introducing a completely independent endpoint, the existing infrastructure is used and expanded to include a targeted view of inactive entries. The result is a precise, reusable mechanism that always gives users a complete overview of all currently inactive shortlinks.</p><h3 id="adjustments-in-the-list-request-activestate-filter">Adjustments in the list request (ActiveState filter)</h3><p>To allow users to search specifically for active, inactive, or all shortlinks, a dedicated activity status filter has been added to the existing list mechanism. This so-called<em>ActiveState filter</em> complements the previous filter criteria, such as text search, sorting, time periods, or pagination, and enables precise control of the search results via the API.</p><p>This extension provides the user with a flexible yet clearly defined way to set the desired status directly via a query parameter. Instead of retrieving the entire list of shortlinks and then filtering it on the client side, the request can now be restricted directly on the server. This saves resources, reduces unnecessary data transfers, and ensures consistent search results.</p><p>The ActiveState filter considers three possible states:</p><ul><li><strong>Active</strong> – The shortlink is active and, if there is an expiration date, has not yet expired.</li><li><strong>Inactive</strong> - The shortlink has been disabled by the user or has expired.</li><li><strong>Not set</strong> – The user does not provide a status indication, so the filter is not applied, and all relevant entries are considered.</li></ul><p>This distinction is communicated via the active query parameter, which is passed to the standard list endpoint in a GET request. The server evaluates this parameter and creates a corresponding<code>UrlMappingFilter</code> object based on it, which controls entry selection.</p><p>The technical implementation of this filter logic is handled in the existing<code>ListHandler</code>, which already handles filtering and sorting shortlinks. The following excerpt shows how to process the active parameter and embed it in the<code>UrlMappingFilter</code>:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">filter</span><span class="p">=</span><span class="nc">UrlMappingFilter</span><span class="p">.</span><span class="n">builder</span><span class="p">()</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">codePart</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="s2">"code"</span><span class="p">))</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">urlPart</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="s2">"url"</span><span class="p">))</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">createdFrom</span><span class="p">(</span><span class="n">parseInstant</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="s2">"from"</span><span class="p">),</span><span class="k">true</span><span class="p">).</span><span class="n">orElse</span><span class="p">(</span><span class="k">null</span><span class="p">))</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">createdTo</span><span class="p">(</span><span class="n">parseInstant</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="s2">"to"</span><span class="p">),</span><span class="k">false</span><span class="p">).</span><span class="n">orElse</span><span class="p">(</span><span class="k">null</span><span class="p">))</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">active</span><span class="p">(</span><span class="n">parseBoolean</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="s2">"active"</span><span class="p">)).</span><span class="n">orElse</span><span class="p">(</span><span class="k">null</span><span class="p">))</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">offset</span><span class="p">(</span><span class="n">offset</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">limit</span><span class="p">(</span><span class="n">size</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">sortBy</span><span class="p">(</span><span class="n">sortBy</span><span class="p">.</span><span class="n">orElse</span><span class="p">(</span><span class="k">null</span><span class="p">))</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">direction</span><span class="p">(</span><span class="n">dir</span><span class="p">.</span><span class="n">orElse</span><span class="p">(</span><span class="k">null</span><span class="p">))</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">build</span><span class="p">();</span></span></span></code></pre></div></div><p>The central point is the line:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="p">.</span><span class="na">active</span><span class="p">(</span><span class="n">parseBoolean</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"active"</span><span class="p">)).</span><span class="na">orElse</span><span class="p">(</span><span class="kc">null</span><span class="p">))</span></span></span></code></pre></div></div><p>Here, the query parameter<code>active</code> is read, interpreted as an optional boolean. The process in detail:</p><ol><li><code>**first(query, "active")**</code> reads the first value of the query parameter<code>active</code> – something like:<ol><li><code>active=true</code></li><li><code>active=false</code></li><li>or the parameter is missing completely.</li></ol></li><li><code>**parseBoolean(...)**</code> converts this value to an<code>Optional&lt;Boolean&gt;</code> . This means that invalid or missing values can also be caught cleanly.</li><li><code>**.orElse(null)**</code> ensures that if input is missing or cannot be interpreted, the value<code>null</code> is transferred to the filter.</li></ol><p>This results in the following meaning:</p><ul><li><code>active=true</code> → only active shortlinks</li><li><code>active=false</code> → only inactive shortlinks</li><li>no parameter → no restriction</li></ul><p>The UrlMappingLookup then processes the resulting UrlMappingFilter:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">int</span><span class="n">total</span><span class="p">=</span><span class="n">store</span><span class="p">.</span><span class="n">count</span><span class="p">(</span><span class="n">filter</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">results</span><span class="p">=</span><span class="n">store</span><span class="p">.</span><span class="n">find</span><span class="p">(</span><span class="n">filter</span><span class="p">);</span></span></span></code></pre></div></div><p>These methods replace the application of the filter to the saved shortlinks. By passing through the<code>active</code>state, the lookup layer can precisely select the entries corresponding to the desired activity state.</p><p>Finally, the results are transferred to the DTO structure and returned as a paginated JSON response:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"/><span class="n">items</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">results</span><span class="p">.</span><span class="na">stream</span><span class="p">().</span><span class="na">map</span><span class="p">(</span><span class="n">m</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">toDto</span><span class="p">(</span><span class="n">m</span><span class="p">,</span><span class="w"/><span class="n">now</span><span class="p">)).</span><span class="na">toList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">return</span><span class="w"/><span class="n">toJsonListingPaged</span><span class="p">(</span><span class="s">"filtered"</span><span class="p">,</span><span class="w"/><span class="n">items</span><span class="p">.</span><span class="na">size</span><span class="p">(),</span><span class="w"/><span class="n">items</span><span class="p">,</span><span class="w"/><span class="n">page</span><span class="p">,</span><span class="w"/><span class="n">size</span><span class="p">,</span><span class="w"/><span class="n">total</span><span class="p">,</span><span class="w"/><span class="n">sortBy</span><span class="p">.</span><span class="na">orElse</span><span class="p">(</span><span class="kc">null</span><span class="p">),</span><span class="w"/><span class="n">dir</span><span class="p">.</span><span class="na">orElse</span><span class="p">(</span><span class="kc">null</span><span class="p">));</span></span></span></code></pre></div></div><p>With this extension, the ActiveState filter fits seamlessly into the existing filter system. Users can now target active or inactive shortlinks without additional endpoints or separate calls. This shows how ActiveState is processed and how it affects the final JSON result.</p><h3 id="http-status-code-semantics-410-vs-404">HTTP status code semantics: 410 vs. 404</h3><p>With the introduction of the active/inactive model, the behaviour of the forwarding logic also changes. For the user, the state of a shortlink must be clearly and unambiguously communicated to the outside world – especially if a redirect cannot be done as expected. For this reason, the system uses two distinct HTTP status codes to distinguish expired and disabled shortlinks.</p><p>A shortlink can no longer be reached for two reasons:</p><ol><li><strong>It has expired.</strong> The expiration date has passed, and the shortlink is no longer valid as planned.</li><li><strong>It has been disabled.</strong> The user has manually disabled the shortlink, regardless of the expiration date.</li></ol><p>Although both result in no redirect, they differ in meaning. Expired shortlinks are intended to signal that their lifespan is ending clearly. Disabled shortlinks, on the other hand, have been deliberately taken out of service and may be in a temporary state.</p><p>Two HTTP status codes represent this semantic separation:</p><ul><li><strong>410 Gone</strong> – for expired short links. This status indicates that the resource is permanently unavailable and will not become available again.</li><li><strong>404 Not Found</strong> – for disabled shortlinks. Although the shortlink exists, its redirect is intentionally not carried out. The 404 status code indicates a temporary or permanent error.</li></ul><p>This distinction provides users, API clients, and monitoring systems with more precise feedback on the shortlink&rsquo;s state. Understandably, a shortlink is not accessible due to a natural process or a manual decision.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Part1-steamPunk.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Part1-steamPunk.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Part1-steamPunk.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - Basic Login Solution - Part 2</title><link>https://svenruppert.com/posts/advent-calendar-2025-basic-login-solution-part-2/</link><pubDate>Thu, 18 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-basic-login-solution-part-2/</guid><description>What has happened so far? In the first part of &ldquo;Basic Login Solution&rdquo;, the foundation for a deliberately simple yet structurally clean admin login was laid. The starting point was the realisation that even a technically lean URL shortener requires a clear separation between public-facing functions and administrative operations. The goal was not complete user management, but rather a minimal access barrier that integrates seamlessly with the existing Java and Vaadin architectures.</description><content:encoded>&lt;![CDATA[<h2 id="what-has-happened-so-far">What has happened so far?</h2><p>In the first part of &ldquo;Basic Login Solution&rdquo;, the foundation for a deliberately simple yet structurally clean admin login was laid. The starting point was the realisation that even a technically lean URL shortener requires a clear separation between public-facing functions and administrative operations. The goal was not complete user management, but rather a minimal access barrier that integrates seamlessly with the existing Java and Vaadin architectures.</p><p>The central component of this solution is a lean, file-based configuration in<em>auth.properties</em>. With just two parameters – an activation switch and a password – the login can be fully controlled. The associated<em>LoginConfigInitializer</em> ensures that this configuration is read when the servlet container starts and remains consistently available to the application. This clearly defines whether and how the protection mechanism takes effect even before any UI is rendered.</p><p>Based on this, an independent login view was introduced that does not require MainLayout. It serves as a clear entry point into the administrative area and separates the login context from the rest of the UI, both visually and technically. The implementation focuses on a simplified user experience: a password field, a clear call to action, and clear feedback on success or failure. At the same time, an important architectural principle of today is already evident here: security logic and UI interaction remain neatly separated.</p><p>After completing Part 1, a functional login is available, but no full access control is yet in place. Without additional measures, unauthenticated users could continue to access administrative views directly, for example, via deep links or saved URLs. This is precisely where the second part comes in.</p><p>In<strong>Part 2, login is effectively enforced for the first time: via central route protection in MainLayout, consistent session management with SessionAuth,</strong> and a clean logout mechanism. This transforms an isolated login screen into a complete, end-to-end authentication flow that reliably protects the application&rsquo;s administrative area.</p><p><strong>The source code for this article can be found on GitHub at the following URL:</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-09">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-09</a></p><p><figure><img src="/images/2025/12/image-57.png" alt="Admin login interface with a prompt to enter the administrator password and a ‘Login’ button." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-59-1024x397.png" alt="Admin interface of a URL shortener application displaying an overview of shortened URLs with options for searching, filtering, and user actions." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-58.png" alt="Header of an admin interface displaying the title ‘EclipseStore’, an item count of ‘3 items’, and a ‘Logout’ button." loading="lazy" decoding="async"/></p><h2 id="route-protection--access-logic">Route Protection &amp; Access Logic</h2><p>With the introduction of admin login, it is not enough to provide only a login page. The decisive factor in the protection&rsquo;s effectiveness is that all administrative views are consistently protected and accessible only to authenticated users. This is precisely where Route Protection comes in: in conjunction with LoginConfig and SessionAuth, it ensures the application clearly distinguishes between logged-in and non-logged-in users.</p><p>Instead of securing each view individually, the implementation uses a single central location: the<code>MainLayout</code>. Since all relevant management views use this layout, it is a natural anchor point for bundling the access logic. As soon as a view with this layout is loaded, the MainLayout can check whether login is enabled and whether the current user is already authenticated. Only when these conditions are met is access to the target view granted.</p><p>There are several benefits to implementing route protection within the layout. On the one hand, the code in individual views remains lean because they do not have to perform security checks themselves. On the other hand, a uniform logic is implemented that applies equally to all protected areas. If the mechanism is later expanded or adapted, a change at a central location can affect behaviour across the entire application.</p><p>From a functional perspective, route protection follows a simple process: when a navigation enters a view that uses MainLayout, it checks whether login is globally enabled before rendering. If this is not the case, the application behaves as before and still allows direct access to all pages. If login is enabled, the logic checks whether the current session is marked as authenticated. If not, the user will be automatically redirected to the login page.</p><p>An exception is the login view itself. It must always be accessible without prior authentication, as it enables access to the protected area. If they were also subject to route protection, an infinite redirect loop would result. The implementation explicitly handles this special case by excluding the login route from the check.</p><figure><img src="/images/2025/12/image-60-369x1024.png" alt="Flowchart illustrating the navigation and authentication process in an admin login system. It includes decision points for checking if login is enabled, the target view, and session authentication status." loading="lazy" decoding="async"/><p>In practice, this mechanism results in transparent, predictable behaviour: non-logged-in users who open a deep link directly into the admin UI are automatically redirected to the login page. After a successful login, you will be taken to the desired administration page and can navigate the protected area without further interruptions. At the same time, the option to altogether turn off the login at the configuration level is retained – in this case, the application behaves as if route protection never existed.</p><h3 id="implementation-in-mainlayout">Implementation in MainLayout</h3><p>The technical implementation of route protection is anchored in the<code>MainLayout</code>. This not only serves as a visual framework for the administration interface but also assumes a central control function for all navigation<code> leading to the protected area</code> via the BeforeEnterObserver interface.</p><p>First, the layout is extended so that it can participate in navigation and, at the same time, receive logging functionality:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">AppLayout</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">BeforeEnterObserver</span><span class="p">,</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span></span></span></code></pre></div></div><p>The implementation of<code>BeforeEnterObserver</code> enables the MainLayout to perform a check before each navigation to a View that uses it. At the same time,<code>HasLogger</code> provides a convenient logging API to make decisions and states in the log traceable.</p><p>The actual route protection is<code>bundled in the</code>beforeEnter method:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">beforeEnter</span><span class="p">(</span><span class="n">BeforeEnterEvent</span><span class="w"/><span class="n">event</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">If</span><span class="w"/><span class="n">login</span><span class="w"/><span class="n">is</span><span class="w"/><span class="n">globally</span><span class="w"/><span class="n">turned</span><span class="w"/><span class="n">off</span><span class="p">,</span><span class="w"/><span class="k">do</span><span class="w"/><span class="n">not</span><span class="w"/><span class="n">protect</span><span class="w"/><span class="n">any</span><span class="w"/><span class="n">routes</span><span class="p">.</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nf">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="w"/><span class="n">LoginConfig</span><span class="p">.</span><span class="na">isLoginEnabled</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"beforeEnter target={} authenticated={}"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">event</span><span class="p">.</span><span class="na">getNavigationTarget</span><span class="p">().</span><span class="na">getSimpleName</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SessionAuth</span><span class="p">.</span><span class="na">isAuthenticated</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">The</span><span class="w"/><span class="n">login</span><span class="w"/><span class="n">view</span><span class="w"/><span class="n">itself</span><span class="w"/><span class="n">must</span><span class="w"/><span class="n">never</span><span class="w"/><span class="n">be</span><span class="w"/><span class="kd">protected</span><span class="p">,</span><span class="w"/><span class="n">otherwise</span><span class="w"/><span class="n">we</span><span class="w"/><span class="n">create</span><span class="w"/><span class="n">a</span><span class="w"/><span class="n">loop</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nf">if</span><span class="w"/><span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="na">getNavigationTarget</span><span class="p">().</span><span class="na">equals</span><span class="p">(</span><span class="n">LoginView</span><span class="p">.</span><span class="na">class</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="w"/><span class="n">SessionAuth</span><span class="p">.</span><span class="na">isAuthenticated</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"beforeEnter.. isAuthenticated()==false - reroute to LoginView"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">event</span><span class="p">.</span><span class="na">rerouteTo</span><span class="p">(</span><span class="n">LoginView</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The logic follows the process described above exactly. First, it is checked whether the login is active per the configuration. If<code>login.enabled is set to false in the auth.properties file, the method returns immediately without any restrictions</code>. In this mode, the application behaves as it did before Route Protection was introduced.</p><p>If login is enabled, the next step is to check the current navigation destination and verify whether the user is already authenticated—a short log entry records which view to navigate to and the current authentication status. If the target is the<code>LoginView</code>, the check is aborted – the login page always remains accessible, regardless of whether the session is already logged in or not.</p><p>For all other views, if the session is not marked as logged in by<code>SessionAuth.isAuthenticated(),</code> the user will be transparently redirected to the login page. The original target view is not rendered, so no administrative functions are visible without authentication.</p><h4 id="logout-button-in-the-header">Logout button in the header</h4><p>Closely linked to route protection is the ability to clean end an existing session. This functionality is also implemented in the<code>MainLayout</code> and appears to the user as a logout button in the header. The creation of this button is linked to the configuration:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="n">HorizontalLayout</span><span class="w"/><span class="n">headerRow</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">LoginConfig</span><span class="p">.</span><span class="na">isLoginEnabled</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">logoutButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Logout"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SessionAuth</span><span class="p">.</span><span class="na">clearAuthentication</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">getPage</span><span class="p">().</span><span class="na">setLocation</span><span class="p">(</span><span class="s">"/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">LoginView</span><span class="p">.</span><span class="na">PATH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logoutButton</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_TERTIARY_INLINE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">headerRow</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">toggle</span><span class="p">,</span><span class="w"/><span class="n">appTitle</span><span class="p">,</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(),</span><span class="w"/><span class="n">storeIndicator</span><span class="p">,</span><span class="w"/><span class="n">logoutButton</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">headerRow</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">toggle</span><span class="p">,</span><span class="w"/><span class="n">appTitle</span><span class="p">,</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(),</span><span class="w"/><span class="n">storeIndicator</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>If the login is deactivated, no logout button is displayed because there is no state to terminate. If the login is active, MainLayout adds a simple inline logout button to the header. Clicking it calls SessionAuth.clearAuthentication(), removes the authentication attribute from the VaadinSession, and closes the session. The browser is then explicitly<code>redirected to the login page</code> via setLocation.</p><p>The combination of<code>the beforeEnter</code> check and the logout button creates consistent access logic: Users are directed to the login page when they first access the protected area, can move freely within the admin UI after successful login, and can end their session at any time in a visible, controlled manner.</p><p>In the next chapter, we will focus on the<code>SessionAuth class</code>, which encapsulates the login status in the session and provides a centralised access point.</p><h2 id="logout-function-in-the-header">Logout function in the header</h2><p>A login mechanism is only complete if it also knows a clear way back. In practice, this means that anyone who logs in to the admin interface must be able to end their session just as consciously. The introduction of a logout function is therefore not only a technical addition but also an essential signal to users that access to the administrative area is considered a sensitive context.</p><p>In the URL shortener, this functionality is shown as a slim logout button in the header. It is visible only if login protection is enabled in the configuration and the application is in &ldquo;protected mode&rdquo;; in all cases where &ldquo;<code>login.enabled=false", the UI does not display this button because</code> there is no session to log off. This deliberately keeps the interface tidy in simple development or demo scenarios.</p><p>From the user&rsquo;s perspective, the logout button is inconspicuously integrated into the existing header. It does not appear as a prominent CTA, but it is always available in the admin area. The naming is deliberately kept clear: &ldquo;Logout&rdquo; leaves no room for interpretation and signals that the current authorisation context is ended here. In environments where multiple people work one after the other using the same browser and admin interface, this clarity is essential.</p><p>Technically, clicking the logout button performs two tasks: first, the authentication status in the current VaadinSession is reset; second, the browser is redirected to the login page. This ensures that the administrative views do not remain open in the browser; subsequent access still follows the login flow. Even an accidentally open tab loses its authorisation as soon as the user logs out.</p><p>The implementation adheres closely to the rest of the login architecture. The button does not check itself whether a session is authenticated; instead, it delegates this to the small helper class SessionAuth, which is also used elsewhere, such as for route protection. This keeps the logout consistent: the same abstraction that determines whether a session is considered logged in is also responsible for deleting this status.</p><figure><img src="/images/2025/12/image-62.png" alt="A flowchart illustrating the logout process in a web application, showing user actions and system responses from selecting logout to ending the session." loading="lazy" decoding="async"/><p>From a UX perspective, the logout function also helps structure users&rsquo; mental model. As long as they are logged in, they are in an active administration context where changes to shortlinks and configurations are expected. When they log out, they deliberately revert to a neutral role, in which they use the generated URLs at most from the user&rsquo;s perspective. This separation is particularly helpful in heterogeneous teams where not all participants need administrative rights simultaneously.</p><h3 id="implementation-in-mainlayout-1">Implementation in MainLayout</h3><p>The logout function is centrally integrated into<code>the MainLayout</code>. The header is built there, and the logout button can be added alongside the app title and the store indicator. Whether this button is visible depends directly on the login configuration:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">HorizontalLayout</span><span class="w"/><span class="n">headerRow</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">LoginConfig</span><span class="p">.</span><span class="na">isLoginEnabled</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">logoutButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Logout"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SessionAuth</span><span class="p">.</span><span class="na">clearAuthentication</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">getPage</span><span class="p">().</span><span class="na">setLocation</span><span class="p">(</span><span class="s">"/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">LoginView</span><span class="p">.</span><span class="na">PATH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logoutButton</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_TERTIARY_INLINE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">headerRow</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">toggle</span><span class="p">,</span><span class="w"/><span class="n">appTitle</span><span class="p">,</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(),</span><span class="w"/><span class="n">storeIndicator</span><span class="p">,</span><span class="w"/><span class="n">logoutButton</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">headerRow</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">toggle</span><span class="p">,</span><span class="w"/><span class="n">appTitle</span><span class="p">,</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(),</span><span class="w"/><span class="n">storeIndicator</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In activated mode, a &ldquo;Logout&rdquo; button is created and added to the header&rsquo;s HorizontalLayout. The look is based on the rest of the UI and uses the<code>LUMO_TERTIARY_INLINE</code> variant, so the button appears discreet and blends visually with the header.</p><p>The actual logoff logic is in the click listener: First,<code>SessionAuth.clearAuthentication()</code> is called to reset the current session&rsquo;s authentication state. The browser is then explicitly redirected to the login view path. This means that the user is not just taken to a &ldquo;blank&rdquo; page after logging out, but is clearly recognisable back to the entry point of the admin area.</p><p>If the login function is globally deactivated, the button is omitted. The<code>headerRow</code> layout then consists only of toggle, title, spacer and<code>StoreIndicator</code>. This deliberately keeps the UI slim in scenarios without login protection and does not display a non-working logout button.</p><h3 id="sessionauth--session-state-management">SessionAuth – Session state management</h3><p>The SessionAuth<code>helper class</code>includes all accesses to the authentication state within the<code>VaadinSession</code>. It is used by both the route protection and the logout button and thus represents the central abstraction layer for login decisions:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.svenruppert.urlshortener.ui.vaadin.security</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.svenruppert.dependencies.core.logger.HasLogger</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.server.VaadinSession</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">com.svenruppert.dependencies.core.logger.HasLogger.staticLogger</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">java.lang.Boolean.TRUE</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">SessionAuth</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">ATTR_AUTH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"authenticated"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="nf">SessionAuth</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isAuthenticated</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">VaadinSession</span><span class="w"/><span class="n">session</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">VaadinSession</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">session</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">attribute</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">session</span><span class="p">.</span><span class="na">getAttribute</span><span class="p">(</span><span class="n">ATTR_AUTH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"isAuthenticated.. {}"</span><span class="p">,</span><span class="w"/><span class="n">attribute</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">TRUE</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">attribute</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">markAuthenticated</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"markAuthenticated.. "</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">VaadinSession</span><span class="w"/><span class="n">session</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">VaadinSession</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">session</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">session</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="n">ATTR_AUTH</span><span class="p">,</span><span class="w"/><span class="n">TRUE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">clearAuthentication</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"clearAuthentication.. "</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">VaadinSession</span><span class="w"/><span class="n">session</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">VaadinSession</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">session</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">session</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="n">ATTR_AUTH</span><span class="p">,</span><span class="w"/><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">session</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The<code>markAuthenticated()</code> method is called after a successful login and sets<code>the simple attribute</code>authenticated<code>to</code>TRUE in the current<code>VaadinSession</code>. With<code>isAuthenticated(),</code> this state can be queried again later – for example, in the route protection of the<code>MainLayout</code>. Both methods use logging to quickly determine when a session was logged in or logged out, in the event of an error, or when analysing user behaviour.</p><p>For the logout,<code>clearAuthentication()</code> is crucial. It removes the attribute from the session and also closes the<code>VaadinSession</code>. This will also discard other session-related data, and an accidentally opened browser tab will lose its permissions. The next time it is accessed, a new, unauthenticated session is established and guided through the login flow.</p><p>This tight integration of<code>MainLayout</code> and<code>SessionAuth</code> creates a robust, yet manageable, logout implementation. The header provides a clearly visible exit from the admin context, and the session logic ensures this exit is implemented consistently from a technical standpoint.</p><p>In the next chapter, we will take a closer look at the SessionAuth class and examine its role in interacting with route protection and the login view within the overall application flow.</p><h2 id="sessionauth-session-based-authentication-in-the-overall-flow">SessionAuth: Session-based authentication in the overall flow</h2><p>Now that login protection, the login page, and the logout function have been introduced, a central question remains: Where is it actually recorded whether a user is authenticated? The answer lies in the small but crucial helper class SessionAuth, which anchors the authentication state in VaadinSession.</p><p>At its core,<code>SessionAuth</code> fulfils three tasks. It can first check whether a current session is already registered. Second, after successful login, it can mark the session as authenticated. And thirdly, it can remove the authentication status when logging out and close the session. These three operations form the basis of the login view, route protection in the MainLayout, and the logout button in the header, all of which access a standard truth value rather than maintaining their own state.</p><p>The chosen approach leverages the fact that Vaadin maintains a separate VaadinSession for each user. This session exists on the server side and is therefore less susceptible to client-side manipulation than, for example, a simple browser flag. By<code>setting a dedicated attribute – such as</code>&ldquo;authenticated&rdquo; – in this session,<code>SessionAuth</code> clearly links the login state to the current browser session. If the same user opens a new browser window, a new session is created; the user is not logged in again.</p><p>In the interaction between components, this results in a precise flow: if a user reaches the login page and enters a correct password, LoginView calls SessionAuth.markAuthenticated() after a successful check. It then navigates to the administrative overview. As soon as the user opens additional views from there, the MainLayout checks whether the session is still considered logged in, as part of the route protection, using<code>SessionAuth.isAuthenticated().</code> If this is the case, navigation is allowed. Otherwise, the request will be redirected to the login page.</p><p>Finally, if the user clicks the logout button in the header, SessionAuth.clearAuthentication() removes the authentication attribute from the session and closes VaadinSession. For the server, this session is over. The following request establishes a new session, which is again considered unauthenticated from the application&rsquo;s perspective, so Route Protection redirects the request back to the login view.</p><p>This session approach fits well with the project&rsquo;s objective: it is deliberately kept simple, requires no additional infrastructure and is easy to understand. At the same time, it meets the requirements of a minimalist admin login, which is less about highly secured, distributed authentication procedures and more about a clear separation between &ldquo;logged in&rdquo; and &ldquo;not logged in&rdquo; within a running browser session.</p><p>In the rest of this chapter, we will go through the implementation of the three methods<code>isAuthenticated</code>,<code>markAuthenticated</code> and<code>clearAuthentication</code> and look at how they interact at the different points in the code – Login-View, MainLayout and Logout-Button.</p><h3 id="the-implementation-of-sessionauth-in-detail">The implementation of SessionAuth in detail</h3><p>The<code>SessionAuth</code> class is deliberately kept compact. It encapsulates access to the<code>VaadinSession</code> and provides three static methods, each of which fulfils a clearly defined task:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.svenruppert.urlshortener.ui.vaadin.security</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.svenruppert.dependencies.core.logger.HasLogger</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.server.VaadinSession</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">com.svenruppert.dependencies.core.logger.HasLogger.staticLogger</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">java.lang.Boolean.TRUE</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">SessionAuth</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">ATTR_AUTH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"authenticated"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="nf">SessionAuth</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isAuthenticated</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">VaadinSession</span><span class="w"/><span class="n">session</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">VaadinSession</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">session</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">attribute</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">session</span><span class="p">.</span><span class="na">getAttribute</span><span class="p">(</span><span class="n">ATTR_AUTH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"isAuthenticated.. {}"</span><span class="p">,</span><span class="w"/><span class="n">attribute</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">TRUE</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">attribute</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">markAuthenticated</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"markAuthenticated.. "</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">VaadinSession</span><span class="w"/><span class="n">session</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">VaadinSession</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">session</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">session</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="n">ATTR_AUTH</span><span class="p">,</span><span class="w"/><span class="n">TRUE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">clearAuthentication</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"clearAuthentication.. "</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">VaadinSession</span><span class="w"/><span class="n">session</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">VaadinSession</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">session</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">session</span><span class="p">.</span><span class="na">setAttribute</span><span class="p">(</span><span class="n">ATTR_AUTH</span><span class="p">,</span><span class="w"/><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">session</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h4 id="isauthenticated--check-if-a-session-is-logged-in"><code>isAuthenticated()</code> – Check if a session is logged in</h4><p>The<code>isAuthenticated() method is the primary read-only</code> interface. It is used, among other things, in the<code>MainLayout</code> to decide whether navigation should be allowed or redirected to the login view as part of route protection. Internally, she first asks about the current<code>VaadinSession</code>. If no session exists (for example, in the very early stages of a request), it returns false immediately.</p><p>If a session exists, the &ldquo;authenticated&rdquo; attribute is read. It is a simple object value set to &ldquo;Boolean.TRUE&rdquo; upon successful login. The method compares this value to<code>TRUE</code> and returns<code>true</code> or<code>false</code> accordingly. The additional log entry helps track how often and with what results this check is invoked in the running system.</p><h4 id="markauthenticated--mark-session-as-logged-in"><code>markAuthenticated()</code> – Mark session as logged in</h4><p>After a successful password check in the<code>LoginView</code>,<code>markAuthenticated()</code> is called. The method fetches the current VaadinSession and sets the &ldquo;<code>authenticated" attribute</code>to<code>TRUE</code>. This updates the session state so that subsequent calls to isAuthenticated() return true for that session.</p><p>Here, too, a log entry ensures that the registration time remains traceable. If unexpected conditions occur in the company – for example, because users report that they are &ldquo;suddenly logged out again&rdquo; – the logs can be used to understand better when sessions were marked or discarded.</p><h4 id="clearauthentication--log-out-and-close-session"><code>clearAuthentication()</code> – log out and close session</h4><p>The third method, clearAuthentication(), is the counterpart to login. The logout button in the MainLayout invokes it and performs two tasks simultaneously. First, it removes the<code>"authenticated"</code> attribute from the current session by setting it to<code>null</code>. This means isAuthenticated() will return false<code> for this session from this point on</code>.</p><p>In the second step, &ldquo;<code>session.close()"</code> is called. This invalidates the entire<code>VaadinSession</code>, including any other attributes that may have been set. This measure ensures that other session-related information is not forwarded and that a new request actually starts with a fresh session.</p><p>In combination with the explicit navigation back to the login page, this results in a clean logout flow: the session loses its authorisation, the user leaves the admin context, and must authenticate again to continue.</p><h3 id="interaction-with-login-view-and-mainlayout">Interaction with Login View and MainLayout</h3><p>In the overall flow of the application,<code>SessionAuth</code> is used in several places:</p><ul><li>In the<code>LoginView, the</code>attemptLogin()<code>method calls</code>SessionAuth.markAuthenticated()<code>after a successful password comparison</code>and then navigates to the overview page.</li><li>In<code>the MainLayout</code>, the beforeEnter() implementation uses SessionAuth.isAuthenticated() to intercept unauthorised access and, if necessary, redirect to the login view.</li><li>The logout button in the header calls<code>SessionAuth.clearAuthentication()</code> and then redirects the user out of the administration context to the login page.</li></ul><p>In this way,<code>SessionAuth</code> centralises all access to the authentication status in a single place. Changes to the session model – such as a different attribute name or additional metadata – are made here and are then consistently available to all consuming components.</p><h2 id="conclusion--outlook">Conclusion &amp; Outlook</h2><p>With the introduction of a simple, configurable admin login, the application gains a clearly defined protection mechanism for all administrative functions without the complexity of a full-fledged authentication framework. The solution is deliberately tailored to the project&rsquo;s characteristics: lightweight, comprehensible, and implemented using pure Java/Vaadin architecture. At the same time, it covers the most essential requirements for minimal access control – from password requests to route protection to a clean logout mechanism.</p><p>The overall system consists of a few clearly separated building blocks: a central configuration file, the LoginConfig for evaluating these values, a simple login view for user interaction, route protection in the MainLayout, and the SessionAuth class for managing session state. These components interlock like gears, creating a process that remains technically sound and intuitive for users.</p><p>At the same time, the implementation is deliberately extensible. The current approach stores the password in plain text in the configuration file and uses it, unchanged, as a byte sequence within the application. This is sufficient for development and internal scenarios, but it is clear that it is not suitable for higher security requirements. However, the architecture leaves room for the following points of expansion:</p><ul><li><strong>Password hashing:</strong> The stored passwords are hashed using a hash function such as SHA-256 or bcrypt, so they are not stored in plaintext on the server.</li><li><strong>Multiple users or roles:</strong> The structure could be expanded to support various administrators with individual passwords.</li><li><strong>Time-limited sessions:</strong> An automatic logout due to inactivity would further enhance security.</li><li><strong>Two-Factor Authentication (2FA):</strong> For more demanding environments, a second level of security – for example via TOTP – could be added.</li><li><strong>External identity providers:</strong> The application could be connected to OAuth 2.0/OpenID Connect in the long term, provided it fits the application scenario.</li></ul><p>However, the strength of the solution lies precisely in its not attempting to anticipate these aspects. It provides a pragmatic, ready-to-use foundation that reliably protects the admin area from unintended access without unnecessarily complicating deployment or development.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Header_01.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Header_01.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Header_01.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - Basic Login Solution - Part 1</title><link>https://svenruppert.com/posts/advent-calendar-2025-basic-login-solution-part-1/</link><pubDate>Wed, 17 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-basic-login-solution-part-1/</guid><description>Introduction The administration interface of a URL shortener is a sensitive area where short links can be changed, removed, or assigned expiration dates. Although the system is often operated on an internal server or in a private environment, protecting this management interface remains a fundamental security concern. Accidental access by unauthorised users can not only lead to incorrect forwarding or data loss, but also undermine trust in the overall system.</description><content:encoded>&lt;![CDATA[<h2 id="introduction">Introduction</h2><p>The administration interface of a URL shortener is a sensitive area where short links can be changed, removed, or assigned expiration dates. Although the system is often operated on an internal server or in a private environment, protecting this management interface remains a fundamental security concern. Accidental access by unauthorised users can not only lead to incorrect forwarding or data loss, but also undermine trust in the overall system.</p><p>With the introduction of a configurable login mechanism, precise access control is introduced, deliberately separating access to management functions from the rest of the system. The login serves as a lightweight security measure that does not require external dependencies, frameworks or time-consuming user management. It is precisely this simplicity – a single password, a simple configuration file and a centred login screen – that makes the solution particularly attractive for small deployments or personal projects.</p><p><strong>The source code for this article can be found on GitHub at the following URL:</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-09">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-09</a></p><p><figure><img src="/images/2025/12/image-52.png" alt="A login page for an admin interface, featuring a password field and a login button. The header reads ‘Admin Login’ with an instruction below prompting the user to enter the administrator password." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-53-1024x397.png" alt="Screenshot of the URL shortener overview page, displaying a list of created short links with options to manage them including actions like viewing, deleting, and filtering by URL." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-51.png" alt="Logo and navigation bar of EclipseStore showing items and logout option." loading="lazy" decoding="async"/></p><h2 id="objectives-of-the-implementation">Objectives of the implementation</h2><p>The introduction of a simple, configurable login mechanism aims to achieve several clearly defined goals that improve both the security and usability of the URL shortener. The focus is not on establishing a complex user and role model, but on creating a pragmatic protective layer that reliably controls administrative access without unnecessarily increasing development or operating costs.</p><p>First, the primary objective is to secure administrative functions. The management interface allows you to create, edit, and remove short links, as well as set or clean expiration dates. These functions must be protected against unauthorised access, particularly if the URL shortener is not operated exclusively within the closed network. A simple login prevents accidental or curious access from causing damage.</p><p>Another goal is to minimise the barrier to entry. The solution should work without additional frameworks, external identity providers, or complex configuration. The implementation is deliberately based on the project&rsquo;s philosophy: Everything remains manageable, lightweight, and understandable. With a simple auth.properties file, the system can be turned on or off at will, and a single password suffices for access control.</p><p>In addition, the login mechanism is designed to ensure a predictable and consistent user experience. These include a well-designed login page, an immediate redirect on unauthenticated access to protected areas, and a transparent logout process that cleanly ends the session. All these aspects ensure that the login fits naturally into the existing UI and is still clearly perceived as a security measure.</p><p>Finally, the login also serves as a foundation for potential future expansions. Although the current implementation is deliberately minimalist, it provides the structural prerequisites to respond to stronger security requirements if necessary—for example, by adding hashing mechanisms, time-limited session tokens, or server-side password rotation. This leaves the system open for further development without losing its current simplicity.</p><h2 id="configurable-login-via-authproperties">Configurable login via<em>auth.properties</em></h2><p>The new login mechanism is based on a deliberately simple configuration file that enables centralised control of the application&rsquo;s authentication behaviour. This file is named<code>auth.properties</code> and is located in the application&rsquo;s resource directory. By using them, the login system can not only be conveniently activated or deactivated, but also quickly adapted to different deployment scenarios.</p><figure><img src="/images/2025/12/image-54-768x1024.png" alt="Flowchart illustrating the evaluation process of login configuration state, detailing the steps and conditions that lead to effective protection states." loading="lazy" decoding="async"/><p>The focus is on two configuration keys:<code>login.enabled</code> and<code>login.password</code>. While the first parameter checks whether the login system is active, the second parameter specifies the required access password. These values are automatically read when the application starts and henceforth determine the behaviour of all protected areas. It is precisely this mechanism that enables turning off login on short notice or changing the password without modifying the source code again.</p><p>The configuration is read in by the LoginConfigInitializer, which is executed when the servlet container starts. It checks whether the file exists, loads its contents and passes it to the central class<code>LoginConfig</code>, which manages these values and makes them available for later access. ``</p><p>This class is responsible for correctly configuring the application&rsquo;s login behaviour at startup. This ensures that both the login page and the route protection can access a uniform configuration basis at all times.</p><p>After loading the file, the<code>LoginConfig</code> class assumes responsibility for persistently storing the values and performing password comparisons.</p><p>This form of configuration control not only simplifies operation but also supports different operating modes. For example, a fully disabled login system is suitable for purely internal development environments, while the activated mode protects against unauthorised changes in production or publicly available environments. Switching between the two modes is kept as low-threshold as possible – a simple value in a text file is enough.</p><p>Although this mechanism provides basic protection, it is<strong>not designed to be a highly secure authentication solution</strong>. Instead, the goal is to establish a minimum level of security to protect the administrative area from accidental access or unauthorised use. For productive environments with increased security requirements, additional measures such as password hashing, multi-factor authentication, or integration into established identity services would be necessary.</p><h2 id="loginconfig--initialization">LoginConfig &amp; Initialization</h2><p>Central control of login behaviour is based on two closely interlinked components: the LoginConfig class itself and its upstream initialiser,<code>LoginConfigInitializer</code>. Together, they ensure that the configuration from the<code>auth.properties</code> file is correctly read, interpreted, and made available to the application runtime.</p><p>The first focus is on the<code>LoginConfig</code> class. It provides a minimalist yet coherent foundation for the login system. The approach is intentionally simple: there is no user base, roles, or profiles, just a single password that serves as an access threshold. The class manages this password and the information on whether the login should be active. The structure remains manageable to keep the entry barrier for administrators and developers low.</p><p>An essential detail is the division into two phases: first, when the application starts, the LoginConfigInitializer checks which settings are stored in the configuration file. These values are then passed to the static<code>LoginConfig.initialise() method, which populates the appropriate fields and makes them available to</code> the rest of the application.</p><p>This initialisation process occurs entirely when the servlet container starts. This ensures that all views loaded later, especially the route protection and the login page, have access to a consistent and fully initialised configuration. This avoids error conditions that could arise from missing or delayed loading of configuration values.</p><p>In the following, we will take a closer look at both components and refer to the source code for clarity.</p><h3 id="loginconfig--central-configuration-class">LoginConfig – central configuration class</h3><p>The core is the<code>LoginConfig</code> class, which stores the login switch and the expected password as byte data. It is declared<code>final</code> and has a private constructor, so it is used exclusively through its static methods:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * Central configuration for the simple admin login.</span></span></span><span class="line"><span class="cl"><span class="cm"> * Reads its values from auth.properties via LoginConfigInitializer.</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">LoginConfig</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">volatile</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">loginEnabled</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">volatile</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">expectedPasswordBytes</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="nf">LoginConfig</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * Initialises the login configuration.</span></span></span><span class="line"><span class="cl"><span class="cm"> *</span></span></span><span class="line"><span class="cl"><span class="cm"> * @param enabled whether the login mechanism should be enforced</span></span></span><span class="line"><span class="cl"><span class="cm"> * @param password the raw password read from configuration, may be {@code null}</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">initialise</span><span class="p">(</span><span class="kt">boolean</span><span class="w"/><span class="n">enabled</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">loginEnabled</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">enabled</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">enabled</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">password</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">password</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">expectedPasswordBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">expectedPasswordBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">password</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(</span><span class="n">StandardCharsets</span><span class="p">.</span><span class="na">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * @return {@code true} if login protection is enabled at all</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isLoginEnabled</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">loginEnabled</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * @return {@code true} if login is enabled and a usable password has been configured</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isLoginConfigured</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">loginEnabled</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">&amp;&amp;</span><span class="n">expectedPasswordBytes</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">expectedPasswordBytes</span><span class="p">.</span><span class="na">length</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * Compares the entered password with the configured one using constant-time comparison.</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">matches</span><span class="p">(</span><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">enteredPassword</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">isLoginConfigured</span><span class="p">()</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">enteredPassword</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">entered</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">String</span><span class="p">(</span><span class="n">enteredPassword</span><span class="p">).</span><span class="na">getBytes</span><span class="p">(</span><span class="n">StandardCharsets</span><span class="p">.</span><span class="na">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MessageDigest</span><span class="p">.</span><span class="na">isEqual</span><span class="p">(</span><span class="n">expectedPasswordBytes</span><span class="p">,</span><span class="w"/><span class="n">entered</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Best</span><span class="o">-</span><span class="n">effort</span><span class="w"/><span class="n">clean</span><span class="o">-</span><span class="n">up</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Arrays</span><span class="p">.</span><span class="na">fill</span><span class="p">(</span><span class="n">entered</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">result</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The<code>initialise</code> method is called only when the initialiser starts and determines whether login is enabled (<code>loginEnabled</code>) and whether a valid password is present. Invalid or empty passwords are consistently discarded; isLoginConfigured() only returns true if there is a usable configuration.</p><p>For the actual password comparison,<code>matches(char[] enteredPassword)</code> serves as the central function. It takes the entered password as a char[], converts it to a byte array in UTF-8 format, and compares it with the expected byte sequence using<code>MessageDigest.isEqual</code>. This enables constant-time comparison, making simple timing attacks more difficult. The temporary byte array is then overwritten using Arrays.fill to remove the sensitive data from memory, at least as effectively as possible.</p><h3 id="loginconfiginitializer--load-configuration-on-startup">LoginConfigInitializer – Load configuration on startup</h3><p>For LoginConfig to work with meaningful values, the configuration file must be read when the servlet container starts. This task is performed by the<code>LoginConfigInitializer</code> class, which<code>is registered</code> as @WebListener and<code>implements the ServletContextListener</code> interface :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@WebListener</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">LoginConfigInitializer</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">ServletContextListener</span><span class="p">,</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">PROPERTIES_PATH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"auth.properties"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">contextInitialized</span><span class="p">(</span><span class="n">ServletContextEvent</span><span class="w"/><span class="n">sce</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Initialising LoginConfig from {}"</span><span class="p">,</span><span class="w"/><span class="n">PROPERTIES_PATH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Properties</span><span class="w"/><span class="n">props</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Properties</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">in</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">getClass</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">getClassLoader</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">getResourceAsStream</span><span class="p">(</span><span class="n">PROPERTIES_PATH</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">in</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">warn</span><span class="p">(</span><span class="s">"No {} found on classpath. Login will be disabled."</span><span class="p">,</span><span class="w"/><span class="n">PROPERTIES_PATH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">LoginConfig</span><span class="p">.</span><span class="na">initialise</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span><span class="w"/><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">props</span><span class="p">.</span><span class="na">load</span><span class="p">(</span><span class="n">in</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">enabledRaw</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">props</span><span class="p">.</span><span class="na">getProperty</span><span class="p">(</span><span class="s">"login.enabled"</span><span class="p">,</span><span class="w"/><span class="s">"true"</span><span class="p">).</span><span class="na">trim</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">enabled</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Boolean</span><span class="p">.</span><span class="na">parseBoolean</span><span class="p">(</span><span class="n">enabledRaw</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">props</span><span class="p">.</span><span class="na">getProperty</span><span class="p">(</span><span class="s">"login.password"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">LoginConfig</span><span class="p">.</span><span class="na">initialise</span><span class="p">(</span><span class="n">enabled</span><span class="p">,</span><span class="w"/><span class="n">password</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">enabled</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Login explicitly disabled via login.enabled=false"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">LoginConfig</span><span class="p">.</span><span class="na">isLoginConfigured</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"LoginConfig initialised successfully from {}"</span><span class="p">,</span><span class="w"/><span class="n">PROPERTIES_PATH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">warn</span><span class="p">(</span><span class="s">"login.enabled=true but no usable password configured. "</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"Login will effectively be disabled."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">error</span><span class="p">(</span><span class="s">"Failed to load "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">PROPERTIES_PATH</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">". Login will be disabled."</span><span class="p">,</span><span class="w"/><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">LoginConfig</span><span class="p">.</span><span class="na">initialise</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span><span class="w"/><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">contextDestroyed</span><span class="p">(</span><span class="n">ServletContextEvent</span><span class="w"/><span class="n">sce</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Nothing</span><span class="w"/><span class="n">to</span><span class="w"/><span class="n">clean</span><span class="w"/><span class="n">up</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The initialiser follows a straightforward process: first, it attempts to load the auth.properties file from the classpath. If this is not possible, the login is explicitly deactivated, and a warning log is written. If successful, the configuration values &ldquo;login.enabled<code>"</code> and &ldquo;<code>login.password"</code> are read, converted to appropriate data types, and<code> forwarded to LoginConfig.initialise</code>. Finally, depending on the configuration, additional log output is generated, providing quick information about the active mode during operation.</p><p>This separation of responsibilities creates a well-traceable initialisation path: LoginConfigInitializer handles loading and interpreting the configuration file. At the same time, LoginConfig encapsulates the actual login logic and is later used by other components, such as the login view or route protection.</p><h2 id="the-new-login-page">The new login page</h2><p>With the introduction of admin login, the application gains its own entry page that deliberately distinguishes itself from the rest of the UI. The new login page serves as the boundary between public functions, such as link forwarding, and sensitive administrative functions in the backend. The goal was to create a reduced, highly focused layout that integrates seamlessly with the application&rsquo;s visual design while remaining clearly recognisable as a security barrier.</p><figure><img src="/images/2025/12/image-56-508x1024.png" alt="Flowchart depicting the login validation process for a URL shortener admin interface, highlighting user requests, authentication checks, password validation, and potential outcomes." loading="lazy" decoding="async"/><p>Instead of being embedded in the existing navigation layout, the login is rendered as a standalone view without<code>MainLayout</code>. Users will not see any page navigation, drawer or admin functions until they have successfully authenticated. The page fills the entire browser window; its contents are centred vertically and horizontally. This creates the impression of a classic login screen with a single task: requesting the password for the administrative area.</p><p>At the centre is a compact login panel comprising a headline, a short explanatory text, a password field, and a login button. The headline clearly conveys that this is admin access, while the accompanying text explains why a password is required and what area it protects. This improves transparency and reduces queries, especially when multiple users use the system.</p><p>The password field is configured to automatically receive focus when the page is loaded. Users can therefore type directly without first clicking with the mouse. In addition, a clear button lets you remove an accidentally entered string with a single click. Displaying the password in plain text is deliberately avoided to reduce the risk of shoulder-surfing, especially in shared work environments.</p><p>The input behaviour is also designed to ensure a smooth process. Authentication can be done either by clicking on the login button or by pressing Enter. If the login fails, the view marks the password field as invalid and displays a clear error message indicating that the entered password is incorrect. This preserves context and allows the user to start a second attempt immediately without reloading the page.</p><h3 id="implementation-of-the-loginview">Implementation of the LoginView</h3><p>The technical implementation of the described login page is handled by the<code>LoginView</code> class. It is registered as a separate route and deliberately dispenses with a layout to enable a focused, full-surface login screen:``</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="n">LoginView</span><span class="p">.</span><span class="na">PATH</span><span class="p">)</span><span class="w"/><span class="c1">// No layout = no navigation or drawer visible</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@PageTitle</span><span class="p">(</span><span class="s">"Admin Login | URL Shortener"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">LoginView</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">BeforeEnterObserver</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">PATH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"login"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">PasswordField</span><span class="w"/><span class="n">passwordField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">PasswordField</span><span class="p">(</span><span class="s">"Password"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">loginButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Login"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">LoginView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setSizeFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setAlignItems</span><span class="p">(</span><span class="n">Alignment</span><span class="p">.</span><span class="na">CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setJustifyContentMode</span><span class="p">(</span><span class="n">JustifyContentMode</span><span class="p">.</span><span class="na">CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">configureForm</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">buildLayout</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>It is already evident from the declaration that LoginView is not embedded in<code>MainLayout</code>. Because the route annotation lacks a layout, the page is displayed in isolation; the layout properties in the constructor handle complete vertical and horizontal centring. The two central UI components – password field and login button – are kept as fields so that they<code>can be further configured in the</code>helper methods configureForm()<code>and</code>buildLayout().</p><p>The configuration of the form is deliberately designed for a lean but fluid user experience:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">configureForm</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">setAutofocus</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">setWidth</span><span class="p">(</span><span class="s">"300px"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">setClearButtonVisible</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">setRevealButtonVisible</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">setInvalid</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">loginButton</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">loginButton</span><span class="p">.</span><span class="na">setWidth</span><span class="p">(</span><span class="s">"300px"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">loginButton</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">attemptLogin</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">addKeyDownListener</span><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="s">"Enter"</span><span class="p">.</span><span class="na">equalsIgnoreCase</span><span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="na">getKey</span><span class="p">().</span><span class="na">getKeys</span><span class="p">().</span><span class="na">getFirst</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">attemptLogin</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">setInvalid</span><span class="p">(</span><span class="kc">false</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The password field automatically gains focus when the page loads and is set to a fixed width, so that the input and button visually form a single unit. The Clear button lets you quickly delete an incorrect entry; the password is displayed in plain text by default. The login button is marked as the primary button and, when pressed, triggers the attemptLogin() method. The value change listener ensures that a previously set error state is restored when typing again.</p><p>The structure of the panel that appears in the middle of the screen is defined in<code>buildLayout():</code></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">buildLayout</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">H2</span><span class="w"/><span class="n">title</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">H2</span><span class="p">(</span><span class="s">"Admin Login"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Paragraph</span><span class="w"/><span class="n">subtitle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"Please enter the administrator password to access the management interface."</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="n">formLayout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">VerticalLayout</span><span class="p">(</span><span class="n">title</span><span class="p">,</span><span class="w"/><span class="n">subtitle</span><span class="p">,</span><span class="w"/><span class="n">passwordField</span><span class="p">,</span><span class="w"/><span class="n">loginButton</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">formLayout</span><span class="p">.</span><span class="na">setSpacing</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">formLayout</span><span class="p">.</span><span class="na">setPadding</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">formLayout</span><span class="p">.</span><span class="na">setAlignItems</span><span class="p">(</span><span class="n">Alignment</span><span class="p">.</span><span class="na">CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">formLayout</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The combination of the heading, explanatory paragraph, password field, and button forms exactly matches the compact login panel described in the previous section. Centring the inner<code>VerticalLayout</code> creates a clear focus on the input area, without visual distractions from other UI elements.</p><p>The actual login process is encapsulated in<code>attemptLogin().</code> This is where configuration and user input are merged:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">attemptLogin</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="w"/><span class="n">LoginConfig</span><span class="p">.</span><span class="na">isLoginEnabled</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"Login is currently disabled. Please check the server configuration."</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">3000</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">Position</span><span class="p">.</span><span class="na">MIDDLE</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">navigate</span><span class="p">(</span><span class="n">OverviewView</span><span class="p">.</span><span class="na">PATH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">input</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">?</span><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">getValue</span><span class="p">().</span><span class="na">toCharArray</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">:</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">char</span><span class="o">[</span><span class="n">0</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="w"/><span class="n">LoginConfig</span><span class="p">.</span><span class="na">isLoginConfigured</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"Login is not configured. Please verify that the configuration file has been loaded."</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">3000</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">Position</span><span class="p">.</span><span class="na">MIDDLE</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">authenticated</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">LoginConfig</span><span class="p">.</span><span class="na">matches</span><span class="p">(</span><span class="n">input</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">authenticated</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SessionAuth</span><span class="p">.</span><span class="na">markAuthenticated</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">navigate</span><span class="p">(</span><span class="n">OverviewView</span><span class="p">.</span><span class="na">PATH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">setErrorMessage</span><span class="p">(</span><span class="s">"Incorrect password"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">setInvalid</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>First, it is checked whether the login is enabled in the configuration. If this is not the case, the user receives clear feedback via notification and is redirected directly to the overview page. The next step is to check whether the login has been configured correctly. Only when both conditions are met is the entered password passed to<code>LoginConfig.matches</code>. If successful, the session is marked as authenticated via<code>SessionAuth.markAuthenticated()</code> and forwarded to the overview. In the event of an error, the view marks the password field as invalid and displays a clear error message – the user remains on the login page and can adjust the input.</p><p>Finally, the implementation of the<code>BeforeEnterObserver</code> ensures that the login page itself only remains visible when it makes sense:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">beforeEnter</span><span class="p">(</span><span class="n">BeforeEnterEvent</span><span class="w"/><span class="n">event</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">If</span><span class="w"/><span class="n">login</span><span class="w"/><span class="n">is</span><span class="w"/><span class="n">disabled</span><span class="p">,</span><span class="w"/><span class="n">skip</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">login</span><span class="w"/><span class="n">page</span><span class="w"/><span class="n">entirely</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nf">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="w"/><span class="n">LoginConfig</span><span class="p">.</span><span class="na">isLoginEnabled</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">event</span><span class="p">.</span><span class="na">forwardTo</span><span class="p">(</span><span class="n">OverviewView</span><span class="p">.</span><span class="na">PATH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">If</span><span class="w"/><span class="n">already</span><span class="w"/><span class="n">authenticated</span><span class="p">,</span><span class="w"/><span class="n">also</span><span class="w"/><span class="n">skip</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">login</span><span class="w"/><span class="n">page</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nf">if</span><span class="w"/><span class="p">(</span><span class="n">SessionAuth</span><span class="p">.</span><span class="na">isAuthenticated</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">event</span><span class="p">.</span><span class="na">forwardTo</span><span class="p">(</span><span class="n">OverviewView</span><span class="p">.</span><span class="na">PATH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">This</span><span class="w"/><span class="n">prevents</span><span class="w"/><span class="n">authenticated</span><span class="w"/><span class="n">users</span><span class="w"/><span class="n">from</span><span class="w"/><span class="n">being</span><span class="w"/><span class="n">redirected</span><span class="w"/><span class="n">to</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">login</span><span class="w"/><span class="n">page</span><span class="w"/><span class="n">again</span><span class="w"/><span class="n">and</span><span class="w"/><span class="n">ensures</span><span class="w"/></span></span></code></pre></div></div><p>This prevents authenticated users from being redirected to the login page again and ensures that a globally deactivated login system does not unnecessarily display a redundant password dialogue.</p><p>Another aspect is the interaction with the configuration. If login in the auth.properties file is disabled or not configured meaningfully, the view displays clear instructions. In one case, it indicates that login is currently disabled and redirects directly to the overview page. In the other case, it suggests that a valid configuration could not be loaded. The page thus serves not only as an input form, but also as a diagnostic point where misconfigurations become visible at an early stage.</p><figure><img src="/images/2025/12/image-55-524x1024.png" alt="Flowchart depicting the authentication process for a password login system, including steps for validation, password comparison, and return of authentication result." loading="lazy" decoding="async"/><p>Overall, the new login page ensures that access to the admin interface is structured, predictable and visually clearly distinguished from the rest of the system. It combines a deliberately simple interaction with a comprehensible security concept, thereby laying the foundation for the other building blocks of the login flow, particularly route protection and session management.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Header_02.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Header_02.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Header_02.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - Mass Grid Operations - Part 2</title><link>https://svenruppert.com/posts/advent-calendar-2025-mass-grid-operations-part-2/</link><pubDate>Tue, 16 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-mass-grid-operations-part-2/</guid><description>What has happened so far&hellip; In the previous part, the URL shortener overview was significantly expanded. The starting point was the realisation that the last UI was heavily optimised for single operations and thus quickly reached its limits as soon as larger volumes of shortlinks needed to be managed. To resolve this bottleneck, the grid was consistently switched to multiple selection, creating the technical basis for actual mass operations.</description><content:encoded>&lt;![CDATA[<h2 id="what-has-happened-so-far">What has happened so far&hellip;</h2><p>In the previous part, the URL shortener overview was significantly expanded. The starting point was the realisation that the last UI was heavily optimised for single operations and thus quickly reached its limits as soon as larger volumes of shortlinks needed to be managed. To resolve this bottleneck, the grid was consistently switched to multiple selection, creating the technical basis for actual mass operations.</p><p>Based on this, a context-dependent bulk action bar was created, visible only when a selection is present. It serves as a central control for simultaneous multi-entry actions and seamlessly integrates them into the existing search, filtering, and grid interaction workflow. The decisive factor was not the mere addition of new buttons, but the clean coupling of UI state, selection and action permission.</p><p>The first concrete mass operation was the bulk delete. This function exemplified how efficiency and security can be combined: through explicit confirmation dialogues, meaningful previews of the affected entries, consistent feedback, and clear visual cues for destructive actions. This workflow has been supplemented with keyboard shortcuts that speed up deleting larger quantities without bypassing the control mechanisms.</p><p>This completes the transition from an entry-centric interface to a professional management view. The overview view now understands selections as a work context and can execute coordinated actions in a controlled manner. The following sections build on this foundation, examining further mass operations, their semantic differences, and their technical implementation in detail.</p><p><strong>The source code for this article can be found on GitHub at the following URL:</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-08">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-08</a></p><p><figure><img src="/images/2025/12/image-48-1024x377.png" alt="Overview interface of a URL shortener tool showing a list of short links, with details such as ‘Shortcode’, ‘URL’, ‘Created’, ‘Expires’, and action buttons." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-49-1024x484.png" alt="Overview of the URL shortener interface, displaying a grid of short links with options to delete selected links or set expiration dates." loading="lazy" decoding="async"/></p><h2 id="bulk-clear-expiry-reliably-and-consistently-remove-expiration-dates">Bulk Clear Expiry: Reliably and Consistently Remove Expiration Dates</h2><p>While setting a uniform expiration date is a typical administrative task, the targeted removal of such expiration dates also plays a central role. In many real-world scenarios, temporary restrictions lose their relevance: links should remain valid permanently, test or campaign parameters must be reset, or previously set durations have proven too restrictive. The ability to delete expiration dates for many shortlinks in a single step is, therefore, a crucial building block for effective mass management.</p><figure><img src="/images/2025/12/image-50-1024x765.png" alt="An overview screen of a URL shortener interface displaying a list of short links with selected options for managing their expiration dates, including a dialogue box asking for confirmation to remove expiration for two selected links." loading="lazy" decoding="async"/><p>The bulk clear expiry function follows a similar interaction pattern to setting an expiration date. Still, it is fundamentally different in its consistency and semantics: while setting introduces a new value, deleting an expiration date explicitly returns to the &ldquo;does not automatically expire&rdquo; state. The architecture must ensure that this state is clearly communicated and implemented correctly.</p><p>The removal of the expiration time must be understood explicitly, not as an implicit side effect, but as a deliberate operation that requires its own confirmation. This ensures both the security of user interactions and the traceability of changes.</p><p>The focus is on the interaction among dialogue logic, API calls, and the changed semantics of the edit endpoint, which now distinguishes between &ldquo;set new expiration date&rdquo; and &ldquo;delete expiration date&rdquo;. This makes the interaction between the UI and the backend clearly comprehensible and demonstrates how reliable mass changes can be integrated cleanly.</p><p>The entry point of the functionality lies in the<code>method confirmBulkClearExpirySelected(),</code> which – analogous to the bulk delete – first validates the current selection and then opens a confirmation dialogue:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">confirmBulkClearExpirySelected</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">selected</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getSelectedItems</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">selected</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"No entries selected"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Dialog</span><span class="w"/><span class="n">dialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">setHeaderTitle</span><span class="p">(</span><span class="s">"Remove expiry for "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">selected</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" short links?"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Text</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"This will remove the expiry date from all selected short links. "</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"They will no longer expire automatically."</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">cancel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">confirm</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Remove expiry"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkClearExpiry</span><span class="p">(</span><span class="n">selected</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">confirm</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">getFooter</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">cancel</span><span class="p">,</span><span class="w"/><span class="n">confirm</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">open</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The structure of this method makes it clear that removing the expiration time is modelled as a standalone operation that requires confirmation. First, an empty selection is excluded; then a dialogue is created whose title explicitly names both the operation type (&ldquo;Remove expiry&rdquo;) and the number of affected entries. The actual content of the dialogue explains the consequence of the action in plain language: The links &ldquo;will no longer expire automatically&rdquo;. This means that semantics are conveyed not only implicitly by the UI but also explicitly by language.</p><p>The actual implementation of the mass operation is carried out in the outsourced method<code>bulkClearExpiry(...)</code>, which encapsulates the transition from the UI layer to the client API:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"> private void bulkClearExpiry(Set<span class="nt">&lt;ShortUrlMapping&gt;</span> selected) {</span></span><span class="line"><span class="cl"> if (selected.isEmpty()) {</span></span><span class="line"><span class="cl"> Notification.show("No entries selected");</span></span><span class="line"><span class="cl"> return;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> int success = 0;</span></span><span class="line"><span class="cl"> int failed = 0;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> for (var m : selected) {</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> boolean ok = urlShortenerClient.edit(</span></span><span class="line"><span class="cl"> m.shortCode(),</span></span><span class="line"><span class="cl"> m.originalUrl()</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl"> if (ok) {</span></span><span class="line"><span class="cl"> success++;</span></span><span class="line"><span class="cl"> } else {</span></span><span class="line"><span class="cl"> failed++;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> } catch (IOException ex) {</span></span><span class="line"><span class="cl"> logger().error("Bulk clear expiry failed for {}", m.shortCode(), ex);</span></span><span class="line"><span class="cl"> failed++;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> grid.deselectAll();</span></span><span class="line"><span class="cl"> safeRefresh();</span></span><span class="line"><span class="cl"> Notification.show("Cleared expiry: " + success + " • Failed: " + failed);</span></span><span class="line"><span class="cl"> }</span></span></code></pre></div></div><p>The signature of the edit call is particularly remarkable. In contrast to the bulk set expiry, only the shortcode and original URL are transferred; the instant and shortcode are not. This API is explicitly implemented in the<code>URLShortenerClient</code> in such a way that the absence of an expiration date is semantically interpreted as &ldquo;delete expiry&rdquo;:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">edit</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">newUrl</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">edit</span><span class="p">(</span><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">newUrl</span><span class="p">,</span><span class="w"/><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">edit</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">newUrl</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiresAtOrNull</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">shortCode</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">shortCode</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"shortCode must not be null/blank"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">newUrl</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">newUrl</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"newUrl must not be null/blank"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">URI</span><span class="w"/><span class="n">uri</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serverBaseAdmin</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">PATH_ADMIN_EDIT</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">shortCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">URL</span><span class="w"/><span class="n">url</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">uri</span><span class="p">.</span><span class="na">toURL</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"edit - {}"</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// ... Request set-up and dispatch ...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>Finally, on the server side, this semantics is anchored in the store implementations. Both the in-memory store and the EclipseStore-based persistence layer now interpret a<code>null</code>value for<code>expiredAt</code> as a signal to remove the expiration time entirely:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">InMemoryUrlMappingStore</span></span><span class="line"><span class="cl">var originalOrNewUrl = url != null ? url : shortUrlMappingOLD.originalUrl();</span></span><span class="line"><span class="cl">var instant = Optional.ofNullable(expiredAt);</span></span><span class="line"><span class="cl">var shortUrlMapping = new ShortUrlMapping(shortCode, originalOrNewUrl,</span></span><span class="line"><span class="cl"> shortUrlMappingOLD.createdAt(), instant);</span></span><span class="line"><span class="cl">store.put(shortUrlMapping.shortCode(), shortUrlMapping);</span></span><span class="line"><span class="cl">EclipseUrlMappingStore</span></span><span class="line"><span class="cl">var originalOrNewUrl = url != null ? url : shortUrlMappingOLD.originalUrl();</span></span><span class="line"><span class="cl">Optional<span class="nt">&lt;Instant&gt;</span> instant = Optional.ofNullable(expiredAt);</span></span><span class="line"><span class="cl">var shortUrlMapping = new ShortUrlMapping(shortCode, originalOrNewUrl,</span></span><span class="line"><span class="cl"> shortUrlMappingOLD.createdAt(), instant);</span></span><span class="line"><span class="cl">urlMappings.put(shortUrlMapping.shortCode(), shortUrlMapping);</span></span><span class="line"><span class="cl">storage.store(dataRoot().shortUrlMappings());</span></span></code></pre></div></div><p>Instead of replacing a missing value with the previous expiration date, expiredAt is now consistently converted to Optional.ofNullable(…). The result is a clear division of semantics: if an<code>instant</code> is passed, the edit operation sets a new expiration date; if<code>null is</code>passed, an empty optional is created, which corresponds to a complete removal of the existing expiration date. This change enables bulkClearExpiry(&hellip;) not only to overwrit<code>e expiry information covertly but also to</code> delete it.</p><p>The combination of a confirmation dialogue, dedicated mass operation, and precisely defined edit semantics creates a continuous path from the user click to the permanently changed data structure. Bulk-Clear-Expiry is not just a &ldquo;special case&rdquo; of the edit function, but a deliberately modelled, independent mass operation with clear, technically and professionally comprehensible meaning.</p><h2 id="keyboard-interaction-as-an-accelerator-for-mass-operations">Keyboard interaction as an accelerator for mass operations</h2><p>With the introduction of mass grid operations, efficient interaction is becoming increasingly important. While mouse gestures and explicit buttons offer high visibility and explainability, keyboard shortcuts are most effective when users perform recurring actions. In the context of the Overview view, this means that central measurement operations should not only be accessible via the bulk bar but also be able to be triggered directly via the keyboard.</p><p>Keyboard interaction fulfils two roles. On the one hand, it serves as a direct accelerator of already established workflows. If you are used to working with keyboard shortcuts, you can trigger searches and deletions without detours via the mouse, reducing noticeable friction, especially with frequent context switches between code, console, and browser. On the other hand, it serves as a semantic condensation of the UI: the presence of a shortcut makes it clear that certain operations are not treated as edge cases but as primary interaction paths.</p><p>Today, this idea is being fleshed out in two prominent places. On the one hand, the global search in the overview view has a dedicated shortcut, allowing you to focus directly on the search field. This starts at the interaction&rsquo;s beginning: a single keyboard shortcut is enough to switch from the web interface&rsquo;s arbitrary state to a precisely defined search mode. On the other hand, the bulk delete operation is bound to the delete key only when a selection is present in the grid. The keyboard thus serves as an equal trigger for the same confirmation dialogue, which would otherwise be accessible only via the bulk bar.</p><p>It is essential for this integration that the keyboard shortcuts are not implemented as hidden, hard-to-discover functions, but as a consistent extension of the existing interaction model. They draw on concepts that have already been introduced – global search, multiple selection, bulk dialogues – and link them to a fluid, operable overall system. If you work exclusively with the mouse, you can use all functions as usual; those who prefer keyboard shortcuts, on the other hand, get much faster access to the same function space.</p><p>In the following, we will show how these shortcuts are technically integrated, how they interact with the grid state model, and which protective mechanisms prevent mass operations from being unintentionally triggered.</p><p>The central entry point is the<code>addShortCuts()</code> method, which bundles all global keyboard shortcuts of the Overview view:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">addShortCuts</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="w"/><span class="n">current</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">current</span><span class="p">.</span><span class="na">addShortcutListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">globalSearch</span><span class="p">.</span><span class="na">isEnabled</span><span class="p">())</span><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">focus</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">},</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Key</span><span class="p">.</span><span class="na">KEY_K</span><span class="p">,</span><span class="w"/><span class="n">KeyModifier</span><span class="p">.</span><span class="na">META</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Bulk delete via Delete-Key</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">current</span><span class="p">.</span><span class="na">addShortcutListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">grid</span><span class="p">.</span><span class="na">getSelectedItems</span><span class="p">().</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">confirmBulkDeleteSelected</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">},</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Key</span><span class="p">.</span><span class="na">DELETE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The method defines two keyboard interactions, each of which fulfils different semantic roles:</p><h3 id="meta--k-focus-on-the-global-search-field"><code>Meta + K</code><strong>– Focus on the global search field</strong></h3><p>The combination of<code>KeyModifier.META</code> (⌘ on macOS, Windows logo key on Windows) and the letter K address an established UI pattern in code editors and command palettes. With a single keystroke, the user can go directly to the global search, regardless of which UI element was previously in focus.</p><p>The protection provided by the following conditions is remarkable:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">globalSearch</span><span class="p">.</span><span class="na">isEnabled</span><span class="p">())</span><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">focus</span><span class="p">();</span></span></span></code></pre></div></div><p>This prevents the shortcut from forcing an interaction that would not make sense semantically in the current UI state – for example, if a dialogue is currently open or the search has been deactivated due to active filter modes. The keyboard interaction thus fits respectfully into the overall system.</p><h3 id="delete-trigger-bulk-delete-if-there-is-a-selection"><code>Delete</code><strong>– Trigger bulk delete if there is a selection</strong></h3><p>The second central key combination binds the delete key directly to the bulk delete function. The shortcut applies to the entire overview view, but is only activated if there is actually a multiple or single selection:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">grid</span><span class="p">.</span><span class="na">getSelectedItems</span><span class="p">().</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">confirmBulkDeleteSelected</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This ensures that the delete dialogue appears only when there is an object context; therefore, it is not possible to trigger an operation without a target by randomly pressing Delete. This protection mechanism is essential for mass operations, as the delete key is often used unconsciously and intuitively.</p><h3 id="integration-into-the-uistate-model">Integration into the UI‑state model</h3><p>Both shortcuts interfere with the grid&rsquo;s state logic. The search shortcut uses the activation logic of the globalSearch field, while the delete shortcut is explicitly bound to the grid selection. This creates a natural coupling between keyboard interaction and UI state: Shortcuts are not detached from the UI, but follow the same semantic structure as mouse interaction via the bulk bar.</p><h3 id="why-it-matters-acceleration-without-side-effects">Why it matters: Acceleration without side effects</h3><p>While shortcuts are often seen as optional conveniences, they play a crucial role in mass operations. Those who manage large amounts of data frequently switch between different tools – IDE, browser, and terminal. The ability to perform central operations without loss of focus and without visual navigation reduces cognitive load and increases speed. At the same time, the built-in protection queries ensure that no unintentional deletion actions are triggered.</p><p>This shows that keyboard interaction is not just a nice extra feature, but an integral part of an efficient, error-robust workflow for mass grid operations, how these shortcuts are integrated into the overview view, how they interact with the state model of the grid, and where protection mechanisms have been deliberately implemented to prevent unintentional mass operations.</p><h2 id="a-consistent-visual-language-for-high-risk-mass-operations">A consistent visual language for high-risk mass operations</h2><p>With the introduction of mass operations, both the functional and visual levels become more complex. Where previously individual actions were placed in isolation in the grid, there are now interconnected chains of interactions that have the potential to have a significant impact on the database – especially in operations such as deleting several shortlinks or changing all expiration dates. This increased impact requires a visual language that creates clarity, clearly highlights risks and helps users to perform each operation consciously and safely.</p><p>An essential goal of this visual language is to establish clear semantic distinctions among action types. While harmless operations – such as opening a detailed dialogue – may be unagitated and neutral, risky or destructive actions must be immediately recognisable as such. The introduction of the bulk action bar makes this differentiation imperative: it bundles both low-risk and high-risk actions in a small space, creating visual clarity, a prerequisite for safe usability.</p><p>The implementation of these principles follows a strict pattern: by targeting Lumo theme variants, buttons are not only styled but also semantically enhanced. Destructive actions are consistently marked with<code>LUMO_ERROR</code>, whereas accompanying or secondary actions are given the more subtle<code>LUMO_TERTIARY_INLINE</code>. This contrast creates an immediately apparent hierarchy, highlighting potentially dangerous actions without visually cluttering the interface.</p><p>The technical basis of this visual language is first evident in the implementation of the bulk action bar. A clear distinction is made here between destructive and non-destructive actions. The corresponding code from the<code>OverviewView</code> clearly shows this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">bulkDeleteBtn</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">LUMO_ERROR</span><span class="p">,</span><span class="w"/><span class="n">LUMO_TERTIARY_INLINE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">bulkSetExpiryBtn</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">LUMO_TERTIARY_INLINE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">bulkClearExpiryBtn</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">LUMO_TERTIARY_INLINE</span><span class="p">);</span></span></span></code></pre></div></div><p>Only the<em>Delete button</em> bears the<code>LUMO_ERROR</code> mark. This signals at a glance that the action could have irreversible effects. The other two buttons – &ldquo;Set expiry&rdquo; and &ldquo;Clear expiry&rdquo; – deliberately remain inconspicuous. Their functions are operational, but not destructive, which is why<code>LUMO_TERTIARY_INLINE</code> offers the appropriate visual restraint here.</p><p>This differentiation also continues at the row level in the grid. Each row has two buttons that play completely different roles visually:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="k">delete</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Icon</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="p">.</span><span class="n">TRASH</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">delete</span><span class="p">.</span><span class="n">addThemeVariants</span><span class="p">(</span><span class="n">LUMO_ERROR</span><span class="p">,</span><span class="w"/><span class="n">ButtonVariant</span><span class="p">.</span><span class="n">LUMO_TERTIARY</span><span class="p">);</span></span></span></code></pre></div></div><p>The delete button is red, making it immediately recognisable. The corresponding detail button is visually restrained:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">details</span><span class="p">=</span><span class="n">new</span><span class="n">Button</span><span class="p">(</span><span class="n">new</span><span class="n">Icon</span><span class="p">(</span><span class="nc">VaadinIcon</span><span class="p">.</span><span class="n">SEARCH</span><span class="p">));</span></span></span><span class="line"><span class="cl"><span class="n">details</span><span class="p">.</span><span class="n">addThemeVariants</span><span class="p">(</span><span class="n">LUMO_TERTIARY_INLINE</span><span class="p">);</span></span></span></code></pre></div></div><p>The user immediately recognises which of the two actions is inconsequential and which is critical. This visual semantics is not cosmetic; it reduces misclicks and improves security, especially in fast workflows. This logic continues in the confirmation dialogues. In the case of bulk delete, the confirmation button is marked twice:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">confirm</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/><span class="p">...</span><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">confirm</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">,</span><span class="w"/><span class="n">LUMO_ERROR</span><span class="p">);</span></span></span></code></pre></div></div><p><code>LUMO_PRIMARY</code> marks the main action in the dialogue,<code>LUMO_ERROR</code> immediately makes it clear that this main action is destructive. The red colouring was deliberately chosen to increase attention and prevent accidental confirmations.</p><p>However, different semantics apply when removing an expiration date. The process is a mass operation, but not destructive. Therefore, the application explicitly does not use a red marking:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">confirm</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Remove expiry"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/><span class="p">...</span><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">confirm</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">);</span></span></span></code></pre></div></div><p>Here, only the primary theme is set – the action is essential, but not dangerous. The UI thus distinguishes between critical and relevant, which is crucial for intuitive usability.</p><p>In summary, the consistent use of Lumo themes creates a clear, recognisable hierarchy:</p><ul><li><code>**LUMO_ERROR**</code> → destructive, potentially irreversible</li><li><code>**LUMO_PRIMARY**</code> → central affirmative action</li><li><code>**LUMO_TERTIARY**</code><strong>/</strong><code>**LUMO_TERTIARY_INLINE**</code> → harmless or accompanying actions</li></ul><p>This visual grammar helps users recognise the seriousness of an action without reading the text. It is precisely this clarity that is indispensable when operations are applied to many data sets rather than a single one.</p><h2 id="conclusion-from-individual-case-to-professional-mass-editing">Conclusion: From individual case to professional mass editing</h2><p>With today&rsquo;s update, the URL shortener&rsquo;s overview view reaches a new level of functionality. What was previously a tool for individual case management is now developing into a full-fledged interface for professional mass editing. The fundamental expansion consists not only in the introduction of several bulk operations, but above all in how these operations are embedded within a consistent operating concept.</p><p>In the previous chapters, it became apparent that the introduction of multiple selection, bulk bar, dialogue mechanisms and a finely graded visual language is not a loose juxtaposition of individual features. Instead, all elements are intertwined: the search structures the data space, the selection defines the area of action, the bulk bar makes the mass operations visible, and the dialogues ensure conscious, comprehensible decisions. Finally, visual semantics provide orientation and support safe operation even in fast or routine workflows.</p><p>This combination of architecture and interaction design provides the foundation for functions that can be added in future expansion stages, including automated workflows, grouped changes, role-based sharing, and even domain-specific mass transformations. These changes thus not only provide a feature package but also the foundation for a productive, reliable, and extensible mass-operations architecture.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Part01-05.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Part01-05.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Part01-05.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - Mass Grid Operations - Part 1</title><link>https://svenruppert.com/posts/advent-calendar-2025-mass-grid-operations-part-1/</link><pubDate>Mon, 15 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-mass-grid-operations-part-1/</guid><description>The next stage of the Advent calendar focuses on further development, which becomes immediately noticeable once more shortlinks are used. The previous interaction patterns in the Overview view were geared toward individual operations and yielded only limited efficiency gains when processing many objects simultaneously. Today, this paradigm is being deliberately broken up and replaced by an interplay of new UI concepts that, for the first time, enable true multi-operation, context-sensitive work, and a much more stringent user interface.</description><content:encoded>&lt;![CDATA[<p>The next stage of the Advent calendar focuses on further development, which becomes immediately noticeable once more shortlinks are used. The previous interaction patterns in the Overview view were geared toward individual operations and yielded only limited efficiency gains when processing many objects simultaneously. Today, this paradigm is being deliberately broken up and replaced by an interplay of new UI concepts that, for the first time, enable true multi-operation, context-sensitive work, and a much more stringent user interface.</p><p><strong>The source code for this article can be found on GitHub at the following URL:</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-08">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-08</a></p><p><figure><img src="/images/2025/12/image-44-1024x377.png" alt="A screenshot of a URL shortener overview dashboard, displaying a list of shortened URLs with corresponding shortcodes, creation dates, and expiration status. The interface includes search functionality, pagination controls, and action buttons for managing entries." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-45-1024x484.png" alt="Overview of the URL Shortener application displaying selected shortlinks with options for deleting, setting expiration dates, and clearing expirations." loading="lazy" decoding="async"/></p><p>The changes focus on three aspects: first, the introduction of a valid multiple selection, which was previously missing and now forms the basis for all new mass operations. Second, the contextual action bar, which appears only when relevant and blends seamlessly into the workflow. Third, a visibly improved depth of interaction, which is reflected in consistent visual cues, reduced refresh cycles, and the ability to perform more complex processing steps – such as setting or removing expiration dates – for any number of entries at the same time.</p><p>These improvements should not be viewed as a collection of isolated features, but rather as a coordinated step toward a more mature UI architecture. They address a common issue in production environments: users manage not just one but dozens or hundreds of shortlinks. Any optimisation that reduces friction losses here directly affects work speed and quality. This is exactly where this step has its impact: the application is not only faster to use but also more tailored to professional use cases.</p><h2 id="multiple-selection-as-the-foundation-for-mass-grid-operations">Multiple selection as the foundation for mass grid operations</h2><p>The transition from a single to a multiple selection marks a fundamental change in the interaction logic of the Overview view. Until now, working with shortlinks has been limited to isolated operations that always followed the same sequence: an entry was selected, edited, confirmed, and the process started over again. This structure was functional but proved to be a stumbling block when handling a larger number of entries in real-world applications.</p><p>With the introduction of multiple selection, this bottleneck will be resolved. The grid transforms from a linear list into a workspace where various objects can be brought into focus simultaneously. This capability is not only an ergonomic improvement, but also forms the technical and conceptual foundation for all subsequent mass operations. Only the possibility of marking any number of shortlinks at the same time makes context-sensitive work possible at all.</p><p>The technical anchoring of this multiple selection is initially done at a very inconspicuous point in the grid configuration. The overview view explicitly switches from single to multi-selection mode, which means that the grid is internally able to keep several entries as selected in parallel:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">configureGrid</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"configureGrid.."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">GridVariant</span><span class="p">.</span><span class="na">LUMO_ROW_STRIPES</span><span class="p">,</span><span class="w"/><span class="n">GridVariant</span><span class="p">.</span><span class="na">LUMO_COMPACT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">setHeight</span><span class="p">(</span><span class="s">"70vh"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">setSelectionMode</span><span class="p">(</span><span class="n">Grid</span><span class="p">.</span><span class="na">SelectionMode</span><span class="p">.</span><span class="na">MULTI</span><span class="p">);</span></span></span></code></pre></div></div><p>With this change, the Vaadin grid&rsquo;s selection mechanism will be expanded from the ground up. While in single-selection mode, the focus is technically always on exactly one entry in Grid.SelectionMode.MULTI allows multiple selections simultaneously. The basic interaction pattern mustn’t change for the user: the choice is still made via familiar gestures, such as clicks, supplemented by keyboard input as needed. The difference lies in the underlying representation: the grid now manages many marked shortlinks rather than a single active element.</p><p>To ensure that this multiple selection is not only visually effective, but also functionally reflected, it is integrated into the rest of the interface via a selection listener:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="n">addSelectionListener</span><span class="p">(</span><span class="n">event</span><span class="o">-&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">all</span><span class="p">=</span><span class="n">event</span><span class="p">.</span><span class="n">getAllSelectedItems</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">boolean</span><span class="n">hasSelection</span><span class="p">=</span><span class="p">!</span><span class="n">all</span><span class="p">.</span><span class="n">isEmpty</span><span class="p">();</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">bulkBar</span><span class="p">.</span><span class="n">setVisible</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">int</span><span class="n">count</span><span class="p">=</span><span class="n">all</span><span class="p">.</span><span class="n">size</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">String</span><span class="n">label</span><span class="p">=</span><span class="n">count</span><span class="o">==</span><span class="mi">1</span><span class="p">?</span><span class="s2">"link selected"</span><span class="p">:</span><span class="s2">"links selected"</span><span class="p">;</span></span></span><span class="line"><span class="cl"><span class="n">selectionInfo</span><span class="p">.</span><span class="n">setText</span><span class="p">(</span><span class="n">count</span><span class="p">+</span><span class="s2">" "</span><span class="p">+</span><span class="n">label</span><span class="p">+</span><span class="s2">" on page "</span><span class="p">+</span><span class="n">currentPage</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">selectionInfo</span><span class="p">.</span><span class="n">setText</span><span class="p">(</span><span class="s2">""</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">bulkDeleteBtn</span><span class="p">.</span><span class="n">setEnabled</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">bulkSetExpiryBtn</span><span class="p">.</span><span class="n">setEnabled</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">bulkClearExpiryBtn</span><span class="p">.</span><span class="n">setEnabled</span><span class="p">(</span><span class="n">hasSelection</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Here, the abstract multiple selection is converted into a concrete UI state. The getAllSelectedItems() method returns the quantity of shortlinks currently marked in the grid. From this, a boolean aggregate state hasSelection is derived, which controls whether the downstream bulk bar is visible and whether the associated action buttons are enabled. At the same time, the selection size provides immediate feedback to the user: the number and context of the selection are summarised in a text component that makes the current page and the number of marked links visible. In this way, multiple-choice is not only managed internally but also translated into a clearly traceable, state-based interaction.</p><p>Of particular relevance is the fact that multiple selection has been seamlessly integrated into the existing UI model. The choice is made via established patterns, such as checkbox clicks or keyboard shortcuts, so users can work intuitively and productively immediately without adapting. At the same time, the visual feedback remains precise: selected rows are clearly highlighted, and changes in the selection immediately update the subsequent contextual UI components.</p><p>Thus, multiple selection provides the necessary foundation for bulk operations, such as deleting, setting or removing expiration times, and other future functions, to be implemented securely and consistently. It is the architectural pivot point for transitioning a purely entry-oriented tool into a scalable management tool for professional use cases.</p><h2 id="the-bulk-action-bar-is-a-context-sensitive-control-centre">The bulk action bar is a context-sensitive control centre.</h2><p>With the introduction of multiple selection, you can, for the first time, edit multiple short links at once. However, this capability alone is insufficient to establish a consistent, high-ergonomics mass-machining process. Only through the bulk action bar, which dynamically appears or disappears based on the current selection, does the overview view become a true hub for mass operations.</p><p>The central idea of this bar is that tools become visible only when they are semantically relevant. As long as no entry is selected, the interface remains tidy and slim. However, as soon as one or more rows have been selected, the function space opens for all operations that refer to a set of objects. This dynamic follows the principle of context-sensitive compression: the UI does not present all possible actions simultaneously, but only those that are meaningful and feasible at the current task.</p><p>In addition, the bulk bar is not only a visual addition but also has a clearly structured internal logic, which is already reflected at the field level of the view. In the overview view, the essential UI building blocks for mass operations are explicitly declared as separate components:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">bulkDeleteBtn</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Delete selected links..."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">bulkSetExpiryBtn</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Set expiry for selected..."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">bulkClearExpiryBtn</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Clear expiry for selected"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Span</span><span class="w"/><span class="n">selectionInfo</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">HorizontalLayout</span><span class="w"/><span class="n">bulkBar</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">();</span></span></span></code></pre></div></div><p>This structure makes it clear that the bulk bar is treated as an independent UI element with its own buttons and a separate information component. The actual integration takes place immediately after the search bar, so that the mass actions are visually and functionally in proximity to global navigation and filtering:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">OverviewView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">H2</span><span class="p">(</span><span class="s">"URL Shortener – Overview"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">initDataProvider</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">buildSearchBar</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">buildBulkBar</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">configureGrid</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addListeners</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addShortCuts</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>In this way, a vertical structure is created in which search, bulk bar, and grid deliberately follow one another: First, the data space is filtered; then the selected elements are contextualised via the bulk bar before the detailed interaction takes place in the grid.</p><p>The behaviour of the bulk bar itself is encapsulated in a dedicated factory method that defines both the visual appearance and the initial functional state:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">Component</span><span class="w"/><span class="nf">buildBulkBar</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkDeleteBtn</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">LUMO_ERROR</span><span class="p">,</span><span class="w"/><span class="n">LUMO_TERTIARY_INLINE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkSetExpiryBtn</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">LUMO_TERTIARY_INLINE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkClearExpiryBtn</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">LUMO_TERTIARY_INLINE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkDeleteBtn</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="s">"Delete all selected short links"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkSetExpiryBtn</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="s">"Set the same expiry for all selected links"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkClearExpiryBtn</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="s">"Remove expiry for all selected links"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkDeleteBtn</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkSetExpiryBtn</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkClearExpiryBtn</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="na">removeAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">selectionInfo</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"opacity"</span><span class="p">,</span><span class="w"/><span class="s">"0.7"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">selectionInfo</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"font-size"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-font-size-s)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">selectionInfo</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"margin-right"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-space-m)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">selectionInfo</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkDeleteBtn</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkSetExpiryBtn</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkClearExpiryBtn</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="na">setWidthFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="na">setDefaultVerticalComponentAlignment</span><span class="p">(</span><span class="n">Alignment</span><span class="p">.</span><span class="na">CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">bulkBar</span><span class="p">.</span><span class="na">setVisible</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">bulkBar</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>In this method, the semantic roles of the individual elements are concretised. The theme variants particularly highlight the delete button, which is provided with an error theme and thus clearly distinguishes itself from less risky operations. The tooltips also enhance the explainability of actions by precisely describing each button&rsquo;s purpose. All buttons start in a deactivated state; only the selection in the grid – as shown in the previous chapter – unlocks it in a targeted manner.</p><p>The layout definition makes it clear that the bulk bar is intended to be a full-width, horizontally aligned control centre. The selectionInfo area is slightly smaller but serves as a permanent context anchor that shows the user which mass scenario they are currently in. The visibility of the entire bar is initially set to<code>false</code> and controlled solely by the grid&rsquo;s selection state. This ensures the bulk bar is visible only when it is needed. It first provides precise information on the selection status by displaying the number of selected shortlinks and the current page. Subsequently, the buttons that can be executed on this basis are unlocked. This mechanism prevents operational errors and ensures the user immediately recognises which actions can currently be performed.</p><h2 id="bulk-delete-secure-and-scalable-deletion-operations-in-the-grid">Bulk Delete: Secure and scalable deletion operations in the grid</h2><p>With the transition to mass operations, deleting multiple shortlinks simultaneously is a key use case. The previous UI was clearly designed for deleting individual entries, which was insufficient for many professional use cases. Especially when it comes to administrative activities, migrations, or cleaning test data, there is a natural need to remove many entries in a single step. This is precisely where the new bulk delete function comes in.</p><p>The key requirement for this function is to balance two aspects: on the one hand, maximum efficiency when handling large volumes of entries, and on the other, high robustness against unintentional or incorrect entries. The system should work quickly, but never delete data carelessly. The implementation, therefore, follows a two-stage interaction pattern, deliberately designed to enable mass processing while ensuring the necessary operational safety.</p><p>The bulk delete always starts with the multiple selection in the grid. Once many shortlinks have been marked, the bulk action bar is activated, and the &ldquo;Delete selected links&hellip;&rdquo; option becomes available. Only when this action is deliberately chosen does a separate confirmation dialogue open. It does more than ask a simple query; it displays sample shortcodes of the selected entries, making the deletion process transparent and verifiable for the user. This preview is essential for larger datasets, as it helps validate the selection&rsquo;s accuracy without reading the entire list of selected items.</p><figure><img src="/images/2025/12/image-47-1024x503.png" alt="Confirmation dialog for deleting selected short links, showing two selected items with their shortcodes and URLs, along with options to delete or cancel." loading="lazy" decoding="async"/><p>The confirmation pattern is designed to trigger the deletion process in a targeted manner without interrupting the workflow. Once confirmed, all highlighted shortlinks will be deleted one by one via the client API. The architecture supports both parallel and sequential deletion patterns; the current implementation uses a sequential approach to capture errors and report them to the UI precisely. Each deletion operation is evaluated, and the user receives a summary of the results that clearly documents both successful and failed deletions.</p><p>This combination of efficiency, security and transparency makes bulk delete a robust tool in a professional context. In the following, the relevant source texts are used to concretise and explain in detail how dialogue logic, API integration and feedback mechanisms interlock to realise a precisely controlled deletion process.</p><p>The entry point for bulk deletion is the confirmBulkDeleteSelected() method in the Overview view. It integrates the grid&rsquo;s current multi-selection with the dialogue logic. It prepares both the preview of the entries to be deleted and the subsequent execution of the delete operations:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">confirmBulkDeleteSelected</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">selected</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getSelectedItems</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">selected</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"No entries selected"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Dialog</span><span class="w"/><span class="n">dialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">setHeaderTitle</span><span class="p">(</span><span class="s">"Delete "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">selected</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" short links?"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">exampleCodes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">selected</span><span class="p">.</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">ShortUrlMapping</span><span class="p">::</span><span class="n">shortCode</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">sorted</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">limit</span><span class="p">(</span><span class="n">5</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">toList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">exampleCodes</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">preview</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">String</span><span class="p">.</span><span class="na">join</span><span class="p">(</span><span class="s">", "</span><span class="p">,</span><span class="w"/><span class="n">exampleCodes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">selected</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">5</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">preview</span><span class="w"/><span class="o">+=</span><span class="w"/><span class="s">", ..."</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Text</span><span class="p">(</span><span class="s">"Examples: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">preview</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Text</span><span class="p">(</span><span class="s">"Delete selected short links?"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">confirm</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">success</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">failed</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kd">var</span><span class="w"/><span class="n">m</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">selected</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">ok</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">urlShortenerClient</span><span class="p">.</span><span class="na">delete</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">ok</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">success</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">failed</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">error</span><span class="p">(</span><span class="s">"Bulk delete failed for {}"</span><span class="p">,</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">(),</span><span class="w"/><span class="n">ex</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">failed</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">deselectAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Deleted: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">success</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" • Failed: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">failed</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">confirm</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">,</span><span class="w"/><span class="n">LUMO_ERROR</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">cancel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">getFooter</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">confirm</span><span class="p">,</span><span class="w"/><span class="n">cancel</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">open</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The first block already clarifies the implementation&rsquo;s security concept. If, contrary to expectations, no selection is made, the process will be aborted with an explicit notification. The actual dialogue instance receives a header that explicitly shows the number of affected shortlinks, thereby clarifying the scope of the action.</p><p>Particularly interesting is how the preview of the entries to be deleted is generated. From the number of selected shortlinks, a sorted list of your shortcodes is created, which is limited to a maximum of five examples. This limitation ensures that the dialogue remains readable even with huge selections, while an appended ellipsis makes it clear that other elements are affected. This creates a concise but meaningful presentation of the selection.</p><p>Finally, the inner<code>Confirm</code> handler performs the actual deletion process. For each shortlink selected, the client API of the URL shortener backend is called via<code>urlShortenerClient.delete(m.shortCode())</code>. Successful and failed operations are counted; Exceptions are explicitly logged and taken into account as errors in the count. This count serves as the basis for the final user notification, which summarises the number of successfully deleted and failed entries.</p><p>After the loop completes, the dialogue is closed, the grid selection is cancelled, and a consistent update of the displayed data is triggered via<code>safeRefresh().</code> The final notification provides precise, aggregated feedback on the operation outcome, thereby completing the interaction cycle of selection, confirmation, and presentation of results.</p><p>In addition, the bulk delete function can also be operated via the keyboard. The user can use a global shortcut to trigger the delete dialogue directly, provided that a selection has been made:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">addShortCuts</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="w"/><span class="n">current</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">current</span><span class="p">.</span><span class="na">addShortcutListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">globalSearch</span><span class="p">.</span><span class="na">isEnabled</span><span class="p">())</span><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">focus</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">},</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Key</span><span class="p">.</span><span class="na">KEY_K</span><span class="p">,</span><span class="w"/><span class="n">KeyModifier</span><span class="p">.</span><span class="na">META</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">current</span><span class="p">.</span><span class="na">addShortcutListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">grid</span><span class="p">.</span><span class="na">getSelectedItems</span><span class="p">().</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">confirmBulkDeleteSelected</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">},</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Key</span><span class="p">.</span><span class="na">DELETE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>This integrates the bulk delete into the everyday workflow: pressing the Delete key triggers the same confirmation dialogue as clicking the corresponding button in the bulk bar. Mouse and keyboard interaction thus yields the same result, significantly speeding up operations for experienced users without compromising the security of the operation.</p><h2 id="bulk-set-expiry-uniform-expiration-dates-for-multiple-shortlinks">Bulk Set Expiry: Uniform expiration dates for multiple shortlinks</h2><p>With the possibility of selecting several entries at the same time, it is possible for the first time to make even more complex metadata changes in a single step. One of the most functionally essential operations in this context is setting uniform expiration dates for multiple shortlinks. This feature is particularly relevant in administrative scenarios, such as when a large number of temporary links are set to expire simultaneously or when test or campaign data needs to be centrally controlled.</p><p>The design of the bulk set expiry function is intended to be both precise and efficient. Users should be able to set the date and time in a consistent dialogue without having to navigate through each entry individually. At the same time, the implementation must robustly handle erroneous inputs and provide clear feedback on the operation&rsquo;s success or failure.</p><p>The dialogue-based setting of a common expiration date follows a three-step interaction pattern. First, the user selects all relevant shortlinks, which makes the bulk bar visible and enables the &ldquo;Set expiry for selected&hellip;&rdquo; option. In the second step, a custom dialogue opens to enter the date and time. Only in the third step, after the conscious confirmation, are the new expiration dates set for all selected entries and the grid is updated accordingly.</p><figure><img src="/images/2025/12/image-46-1024x776.png" alt="User interface of a URL shortener showing selected links with options to delete or set expiry, including a dialog box for setting the expiry date and time for multiple links." loading="lazy" decoding="async"/><p>The entry point into this functionality is the<code>openBulkSetExpiryDialog() method</code>, which is triggered directly from the bulk action bar. It encapsulates both the dialogue-based recording of the expiration time and the later forwarding to the server API:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">openBulkSetExpiryDialog</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">selected</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getSelectedItems</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">selected</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"No entries selected"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Dialog</span><span class="w"/><span class="n">dialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">setHeaderTitle</span><span class="p">(</span><span class="s">"Set expiry for "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">selected</span><span class="p">.</span><span class="na">size</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" short links"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">DatePicker</span><span class="w"/><span class="n">date</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DatePicker</span><span class="p">(</span><span class="s">"Date"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">TimePicker</span><span class="w"/><span class="n">time</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TimePicker</span><span class="p">(</span><span class="s">"Time"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">time</span><span class="p">.</span><span class="na">setStep</span><span class="p">(</span><span class="n">Duration</span><span class="p">.</span><span class="na">ofMinutes</span><span class="p">(</span><span class="n">15</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">HorizontalLayout</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">date</span><span class="p">,</span><span class="w"/><span class="n">time</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">body</span><span class="p">.</span><span class="na">setAlignItems</span><span class="p">(</span><span class="n">Alignment</span><span class="p">.</span><span class="na">END</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">body</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">cancel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Cancel"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Button</span><span class="w"/><span class="n">apply</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Apply"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">date</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Please select a date"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">localTime</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">time</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="n">LocalTime</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">zdt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ZonedDateTime</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="n">date</span><span class="p">.</span><span class="na">getValue</span><span class="p">(),</span><span class="w"/><span class="n">localTime</span><span class="p">,</span><span class="w"/><span class="n">ZoneId</span><span class="p">.</span><span class="na">systemDefault</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiresAt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">zdt</span><span class="p">.</span><span class="na">toInstant</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">success</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">failed</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">m</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">selected</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">ok</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">urlShortenerClient</span><span class="p">.</span><span class="na">edit</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">originalUrl</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">expiresAt</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">ok</span><span class="p">)</span><span class="w"/><span class="n">success</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">else</span><span class="w"/><span class="n">failed</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">error</span><span class="p">(</span><span class="s">"Bulk set expiry failed for {}"</span><span class="p">,</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">(),</span><span class="w"/><span class="n">ex</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">failed</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">deselectAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Updated expiry: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">success</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" • Failed: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">failed</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">apply</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">getFooter</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">cancel</span><span class="p">,</span><span class="w"/><span class="n">apply</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">open</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The method header already includes a security prompt that prevents the dialogue from opening accidentally without a valid selection. The dialogue configuration is deliberately kept minimalist and focuses on the core input elements: a<code>DatePicker</code> for the date and a<code>TimePicker</code> for the time. The TimePicker&rsquo;s step size is set to 15 minutes, which is a practical compromise between precision and usability.</p><p>The core logic begins with validating the date. If no date is selected, the execution is cancelled, and a corresponding notification is issued. If a date is specified, the method creates a<code>ZonedDateTime from it</code>, which is then converted to an<code>instant</code>. This<code>instant</code> represents the new expiration time to be assigned to all selected shortlinks.</p><p>The following loop block illustrates the interface to the backend. For each shortlink, the edit operation is called:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">urlShortenerClient</span><span class="p">.</span><span class="na">edit</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">(),</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">originalUrl</span><span class="p">(),</span><span class="w"/><span class="n">expiresAt</span><span class="p">);</span></span></span></code></pre></div></div><p>This operation is designed to change only the expiration time, while preserving the original URL value. Errors are handled individually and accounted for in both the UI and logging. Summing up the successful and failed updates results in aggregated feedback at the end of the process, which immediately tells the user how successful the bulk operation was.</p><p>Finally,<code>grid.deselectAll()</code> combined with safeRefresh() ensures that both the UI state and the grid data are updated consistently. The user is thus placed in a clean reset state without any manual intermediate steps, while the final Notification.show(&hellip;) summarises the operation results. The bulk set expiry function illustrates the interplay among the UI, server API, and error handling, and explains how this mechanism greatly simplifies managing large numbers of links.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Part01-04.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Part01-04.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Part01-04.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - From UI Interactions to a Deterministic Refresh Architecture</title><link>https://svenruppert.com/posts/advent-calendar-2025-from-ui-interactions-to-a-deterministic-refresh-architecture/</link><pubDate>Sun, 14 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-from-ui-interactions-to-a-deterministic-refresh-architecture/</guid><description>After the first part explained the conceptual basics and the new interactions among global search, search scopes, and advanced filters, the second part focuses on the technical mechanisms that enable these interactions. It is only the revised refresh architecture – above all the interaction of safeRefresh() and RefreshGuard – that ensures that the OverviewView remains calm, deterministic and predictable despite numerous potential triggers.</description><content:encoded>&lt;![CDATA[<p>After the first part explained the conceptual basics and the new interactions among global search, search scopes, and advanced filters, the second part focuses on the technical mechanisms that enable these interactions. It is only the revised refresh architecture – above all the interaction of<code>safeRefresh()</code> and<code>RefreshGuard</code> – that ensures that the OverviewView remains calm, deterministic and predictable despite numerous potential triggers.</p><p>So while Part 1 describes<strong>what</strong> has changed in the user interface and<strong>why</strong> this structuring was necessary, Part 2 now shows in detail<strong>how</strong> the internal state machine works, how competing UI events are coordinated and why the View achieves the desired robustness in the first place.</p><p>With this foundation, the following chapters can be assigned clearly: they analyse the refresh architecture, the reset mechanism, and the validation and error-handling logic – the technical building blocks that ensure the UI concepts described above not only look good but also function reliably.</p><p><strong>The source code for this development step can be found on GitHub</strong></p><p><strong>and can be found here:</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-07">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-07</a></p><p>Here are some screenshots of the current development state.</p><p><figure><img src="/images/2025/12/image-38-1024x525.png" alt="Screenshot of the URL Shortener overview, featuring search bar, filter options, and a table listing shortcodes, URLs, created dates, expiry information, and action buttons." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-39-1024x946.png" alt="Screenshot of the URL Shortener Overview interface, displaying search filters, pagination options, and a table listing shortened URLs along with their details." loading="lazy" decoding="async"/></p><h2 id="refresh-architecture-with-refreshguard">Refresh architecture with RefreshGuard</h2><p>With the introduction of advanced filtering logic, not only does the complexity of the user interface increase, but so does the number of potential conditions that can cause a grid reload. Any change to a filter field, any adjustment to the page size, the opening or closing of the Advanced area, or entering the view itself can trigger a new request to the server. Without additional control, this could trigger a cascade of overlapping refreshes, create unnecessary load, and noticeably degrade the user experience.</p><p>Against this background, the refresh architecture of the OverviewView has been specifically revised. The central goal was to reduce the number of possible triggers to a controllable mechanism that exhibits consistent, predictable behaviour across all relevant scenarios. Instead of allowing each UI event to have its own access to the DataProvider, a deliberate hard cut was made: the decision of whether and when a refresh may actually take place is bundled in a dedicated layer. This layer contains the<code>RefreshGuard</code>, which acts as a guardian that determines which processes are allowed to retrieve specific data and which are only allowed to change the internal state.</p><p>The technical basis of this control system is surprisingly simple at first. A single flag is introduced at the head of the class that controls whether refreshes are currently allowed:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">suppressRefresh</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">false</span><span class="p">;</span></span></span></code></pre></div></div><p>This flag is evaluated by a small helper method that acts as the only allowed entry point for data updates:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">safeRefresh</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"safeRefresh"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">suppressRefresh</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"refresh"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dataProvider</span><span class="p">.</span><span class="na">refreshAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Instead of reloading the grid or DataProvider from multiple locations, the view periodically calls<code>safeRefresh().</code> The method first logs the refresh attempt, then checks the flag. Only if<code>suppressRefresh</code> is not set,<code>refreshAll()</code> is actually executed on the DataProvider. This creates a clear separation between the semantic intent to update the content and the operational decision of whether it is allowed in the current context.</p><p>Conceptually, the<code>RefreshGuard</code> can be understood as a critical section that allows multiple UI operations to be combined. As long as the code is within such a protected phase, direct refresh calls are suppressed. Only at the end of the section does the guard decide whether an aggregated refresh is required and, if so, execute it in a controlled manner. In the source code, this mechanism is implemented as a small, inner helper class:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">RefreshGuard</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">AutoCloseable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">prev</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">refreshAfter</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">RefreshGuard</span><span class="p">(</span><span class="kt">boolean</span><span class="w"/><span class="n">refreshAfter</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">prev</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">suppressRefresh</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">refreshAfter</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">refreshAfter</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">suppressRefresh</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">close</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">suppressRefresh</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">prev</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">refreshAfter</span><span class="p">)</span><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The constructor of the guard first stores the current state of<code>suppressRefresh</code> and then sets the flag to<code>true</code>. This means that all<code>safeRefresh()</code> operations called within this block are ineffective; they are logged but do not trigger a reload. When leaving the block (i.e., in the close() method), the previous flag value is restored. Optionally, a final refresh can then be triggered by the<code>refreshAfter parameter</code>. By implementing<code>AutoCloseable</code>, the guard can be used in a try-with-resources block, ensuring that the state is reset correctly even in exceptional cases.</p><p>The use of this pattern is particularly evident in the View lifecycle. When attaching the OverviewView to the UI, the column visibilities are first set based on the stored preferences. This process changes the grid state significantly but should not cause new data to be loaded from the server multiple times. Accordingly, the entire block is wrapped in a<code>RefreshGuard</code> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">protected</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">onAttach</span><span class="p">(</span><span class="n">AttachEvent</span><span class="w"/><span class="n">attachEvent</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">super</span><span class="p">.</span><span class="na">onAttach</span><span class="p">(</span><span class="n">attachEvent</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="kd">var</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RefreshGuard</span><span class="p">(</span><span class="kc">true</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">keys</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grid</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">getColumns</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">Grid</span><span class="p">.</span><span class="na">Column</span><span class="p">::</span><span class="n">getKey</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">Objects</span><span class="p">::</span><span class="n">nonNull</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">toList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">vis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">columnVisibilityService</span><span class="p">.</span><span class="na">mergeWithDefaults</span><span class="p">(</span><span class="n">keys</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getColumns</span><span class="p">().</span><span class="na">forEach</span><span class="p">(</span><span class="n">c</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">k</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">c</span><span class="p">.</span><span class="na">getKey</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">k</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="n">c</span><span class="p">.</span><span class="na">setVisible</span><span class="p">(</span><span class="n">vis</span><span class="p">.</span><span class="na">getOrDefault</span><span class="p">(</span><span class="n">k</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">subscription</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">StoreEvents</span><span class="p">.</span><span class="na">subscribe</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">getUI</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">ui</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(</span><span class="k">this</span><span class="p">::</span><span class="n">safeRefresh</span><span class="p">)));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Within the try block,<code>suppressRefresh</code> is set to<code>true</code>; all grid operations run without immediate data retrieval. Only when leaving the block and all column visibilities are set consistently does the guard provide a single final refresh with<code>refreshAfter = true</code>. As a result, the view is loaded exactly once after the first setup, rather than flickering through intermediate states during initialisation.</p><p>safeRefresh() also demonstrates its strengths when navigating between pages. The buttons for scrolling forward and backwards only change the internal page cursor and explicitly delegate the data update to the central method:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">prevBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">currentPage</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">1</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="o">--</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refreshPageInfo</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">nextBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">pageSize</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="n">25</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">maxPage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">ceil</span><span class="p">((</span><span class="kt">double</span><span class="p">)</span><span class="w"/><span class="n">totalCount</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">size</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">currentPage</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">maxPage</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refreshPageInfo</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">pageSize</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">setPageSize</span><span class="p">(</span><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="n">25</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Here, the use of the guard is deliberately avoided, as each of these actions constitutes a clearly demarcated, independent refresh. Scrolling back or forward to a new page section, or flipping forward, should trigger a change in page size immediately. Nevertheless, all accesses continue to run via<code>safeRefresh(),</code> so that the mechanism remains centrally controllable. If the architecture changes in the future, an adjustment at this point is sufficient to modify the behaviour of the entire view consistently.</p><p>Taken together, the combination of<code>safeRefresh()</code> and<code>RefreshGuard</code> transforms the refresh behaviour of the OverviewView from a hard-to-predict byproduct of many UI events to a controlled, deterministic strategy. Complex operations such as initialisation and reset are packed into closed, atomic blocks, while simple actions such as page changes and field changes are explicitly allowed to trigger a refresh. This gives the view both stability and transparency: readers of the code can clearly see where data updates occur, and users of the interface perceive a quiet, responsive application that responds to inputs in a comprehensible way.</p><h2 id="reset-mechanism-full-state-clear">Reset Mechanism: Full State-Clear</h2><p>The reset mechanism of the OverviewView plays a special role within the search and filter architecture. It forms the fastest way back to a clearly defined, neutral initial state – a state in which neither search fragments nor extended filters, sorting options, nor deviating page sizes are active. While earlier implementations often reset only partial aspects, the revised version takes a consistently holistic approach: clicking &ldquo;Reset&rdquo; deletes all user-changed parameters without exception and resets the view as if it were being opened for the first time.</p><p>Conceptually, this mechanism is directly integrated into the previously introduced refresh architecture. Since the reset involves a large number of individual steps – emptying text fields, resetting checkboxes and date entries, restoring the default sorting, resetting the page size and closing the Advanced area – each of these actions would immediately trigger a refresh when viewed in isolation. To prevent this, and to treat all changes as logical operations, the entire reset process is embedded within a RefreshGuard.</p><p>The reset button implementation directly reflects these considerations. The listener for the reset button encapsulates all the necessary steps in a single, clearly defined block:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">resetBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="kd">var</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RefreshGuard</span><span class="p">(</span><span class="kc">true</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">codePart</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">codeCase</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">urlPart</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">urlCase</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fromDate</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fromTime</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">toDate</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">toTime</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">sortBy</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dir</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pageSize</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="n">25</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">sortBy</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"createdAt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dir</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"desc"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">searchScope</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"URL"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">advanced</span><span class="p">.</span><span class="na">setOpened</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setSimpleSearchEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">focus</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>The reset process begins by clamping a RefreshGuard with the parameter set to<code>true</code>. This first sets the<code>suppressRefresh flag, so that all</code>safeRefresh() calls<code>indirectly triggered in this block</code>‑remain ineffective. Only when leaving the block (controlled by refreshAfter = true) is a final refresh executed, making the cumulative new state visible in the grid.</p><p>Within the block, all user inputs are systematically returned to their original state. First, all search and filter fields are cleared: the global search field, the shortcode and URL fragments in the Advanced area, and the case-sensitivity checkboxes. The date and time fields for the time window under consideration are then set to<code>zero</code>. This ensures that no old period inadvertently affects later requests.</p><p>In the next step, the sorting is reset to its default values. First,<code>sortBy</code> and<code>dir</code> are removed to avoid potential inconsistencies; then,<code>createdAt is explicitly set</code>as the sort field and<code>desc</code> as the sort direction. The page size is also deliberately set back to the default value of 25 entries per page, and the page cursor<code>currentPage</code> is set to one. This creates a state that corresponds to the first time you enter the view: no running filters, a defined sorting and a comprehensible page setting.</p><p>The global search logic is also reinitialised. The search scope is reset to URL, and the Advanced scope is closed by<code>Advanced.setOpened(false</code> ). Calling<code>setSimpleSearchEnabled(true)</code> re-enables simple mode, and<code>globalSearch.focus()</code> ensures that the cursor lands directly in the global search field. This results in an intuitive process for the user: After the reset, he sees a neutral overview and can immediately start a new, simple search.</p><p>This keeps the user interface completely quiet during the reset: no flickering, no multiple queries, no inconsistent intermediate layout. Only when the entire process is complete is a single, final refresh executed, which establishes a consistent initial state across the grid. This stability is not only crucial for the user experience but also facilitates extensibility, as additional reset steps can be added without introducing new side effects. As long as new fields are included in this guard block, they remain part of the same atomic operation.</p><p>In combination with the search and filter logic, this results in a reset mechanism that is both semantically and technically cleanly modelled. Semantic because the user has a clear expectation – &ldquo;everything back to square one&rdquo; – that is fully met. Technically, because the mechanism is embedded in the central refresh architecture by the RefreshGuard, it causes neither uncontrolled side effects nor hidden data retrievals. On this basis, subsequent chapters can now address error cases, validations, and logging in a more granular way without touching the basic reset path again.</p><h2 id="error-handling-validation-and-robustness">Error handling, validation, and robustness</h2><p>With the growing number of functions in OverviewView, robustness is increasingly critical. The user interface should not only work reliably in ideal scenarios but also handle incomplete, contradictory, or incorrect inputs. The combination of global search, extended filter area, time windows, sorting settings and page size in particular presents the system with the challenge of recognising and stably handling even complex and potentially conflict-prone states.</p><p>In the revised architecture, robustness is not treated as an afterthought, but as an integral part of the UI logic. Many validation and error-avoidance mechanisms are deeply embedded in interaction points: in ValueChange listeners, when switching between Simple and Advanced modes, and when resetting and deriving a binding search string. The view does not aim to take away all user freedom, but instead offers a controlled environment in which only consistent states can arise. The technical side deliberately avoids complex error messages in favour of clear, deterministic rules that become directly visible in the interaction of the input elements.</p><p>A central element of this robust logic is the automatic verification of consistency for the search and filter fields. The pattern of defensive synchronisation is already clearly evident in the global search field:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">globalSearch</span><span class="p">.</span><span class="n">addValueChangeListener</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">v</span><span class="p">=</span><span class="nc">Optional</span><span class="p">.</span><span class="n">ofNullable</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">getValue</span><span class="p">()).</span><span class="n">orElse</span><span class="p">(</span><span class="s2">""</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">searchScope</span><span class="p">.</span><span class="n">getValue</span><span class="p">().</span><span class="n">equals</span><span class="p">(</span><span class="s2">"Shortcode"</span><span class="p">))</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="n">v</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="n">v</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>This logic ensures that the shortcode and URL fragment cannot be active simultaneously. As soon as the user enters something in the global search box, the value is interpreted as either a shortcode or a URL. The other field is consistently emptied. In this way, there are no conflicting filter states that would force the application to fulfil two search intentions simultaneously.</p><p>The Scope Selection listener reinforces this rule by ensuring that even subsequent changes to the search scope always result in a consistent state:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">searchScope</span><span class="p">.</span><span class="n">addValueChangeListener</span><span class="p">(</span><span class="n">_</span><span class="o">-&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">v</span><span class="p">=</span><span class="nc">Optional</span><span class="p">.</span><span class="n">ofNullable</span><span class="p">(</span><span class="n">globalSearch</span><span class="p">.</span><span class="n">getValue</span><span class="p">()).</span><span class="n">orElse</span><span class="p">(</span><span class="s2">""</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="s2">"Shortcode"</span><span class="p">.</span><span class="n">equals</span><span class="p">(</span><span class="n">searchScope</span><span class="p">.</span><span class="n">getValue</span><span class="p">()))</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="n">v</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="n">v</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>This prevents a user from typing a search query in URL mode, then switching to shortcode, thereby implicitly creating an invalid search model. The UI detects this state early and maps it to a clear, comprehensible model.</p><p>The validation and cleanup mechanisms are particularly evident in advanced mode when deriving a valid simple search state. The<code>applyAdvancedToSimpleAndReset() method</code> is the technical condensation of this approach:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">applyAdvancedToSimpleAndReset</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">codePart</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="s">""</span><span class="p">).</span><span class="na">trim</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">url</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">urlPart</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="s">""</span><span class="p">).</span><span class="na">trim</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">hasCode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="o">!</span><span class="n">code</span><span class="p">.</span><span class="na">isBlank</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">hasUrl</span><span class="w"/><span class="o">=</span><span class="w"/><span class="o">!</span><span class="n">url</span><span class="p">.</span><span class="na">isBlank</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">winnerValue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">hasCode</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">code</span><span class="w"/><span class="p">:</span><span class="w"/><span class="p">(</span><span class="n">hasUrl</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">url</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">winnerScope</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">hasCode</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"Shortcode"</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"URL"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="kd">var</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RefreshGuard</span><span class="p">(</span><span class="kc">true</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">codePart</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">codeCase</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">urlPart</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">urlCase</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fromDate</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fromTime</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">toDate</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">toTime</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">sortBy</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dir</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">sortBy</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"createdAt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dir</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"desc"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">searchScope</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="n">winnerScope</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">winnerValue</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="n">winnerValue</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setSimpleSearchEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">focus</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Several basic principles of robustness are intertwined here. First, it checks whether a shortcode or a URL fragment is set. If both are present, the shortcode fragment is given priority – a clear and comprehensible rule that avoids ambiguity. The entire Advanced area is then consistently cleaned up to ensure that no old or partially set values are unintentionally included in future filters.</p><p>In addition,<code>RefreshGuard</code> plays a special role in robust processes. Without the guard, the numerous changes within this method would trigger a series of refresh events. However, the guard suppresses these events selectively and triggers exactly one consistent refresh at the end. This prevents flickering UI transitions and ensures the user always sees the final state.</p><p>Another important component is validating time windows. The combination of DatePicker and TimePicker can naturally generate incomplete entries – such as a set date without time or vice versa. The logic in the backend transport takes care of these cases, but already in the UI code, a defensive determination of the timestamp prevents potential errors:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private Optional<span class="nt">&lt;Instant&gt;</span> combineDateTime(DatePicker date, TimePicker time) {</span></span><span class="line"><span class="cl"> var d = date.getValue();</span></span><span class="line"><span class="cl"> var t = time.getValue();</span></span><span class="line"><span class="cl"> if (d == null<span class="err">&amp;&amp;</span> t == null) return Optional.empty();</span></span><span class="line"><span class="cl"> if (d == null) return Optional.empty();</span></span><span class="line"><span class="cl"> var lt = (t != null) ? t : LocalTime.MIDNIGHT;</span></span><span class="line"><span class="cl"> return Optional.of(lt.atDate(d).atZone(ZoneId.systemDefault()).toInstant());</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The method is generous, but clear: a time value without a date is not a valid filter. In case of doubt, times are set to midnight, which keeps the model stable even in incomplete scenarios. This type of defensive modelling prevents incomplete UI inputs from leading to inconsistent backend requests.</p><p>The event handlers provide additional technical protection for paging and navigation. Actions such as scrolling between pages or changing the page size have a direct effect on the database, but should not trigger any unexpected side effects. The consistent use of<code>safeRefresh()</code> ensures that these changes only take effect if the refresh context allows it:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">pageSize</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">setPageSize</span><span class="p">(</span><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="n">25</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeRefresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Here, too, robustness is created by clear rules: A new page size resets the page cursor and triggers a controlled reload – never several, never via a detour.</p><p>Finally, logging also contributes significantly to diagnostic robustness. In many places in the code,<code>logger().info()</code> is deliberately used to signal when search changes, refresh processes, or state transitions occur. These traces enable precise reconstruction of complex error patterns in UI-backend interactions without requiring additional debugging mechanisms.</p><p>The result is a system that does not rely on errors that are subsequently intercepted, but on deliberately modelled, conflict-free states. The user guidance is designed to prevent invalid or contradictory situations, and the technical foundation ensures that even incomplete or ambiguous inputs are converted into stable, controlled processes. Thus, the combination of validation, synchronisation, and protection mechanisms provides a viable basis for further expansion of the application.</p><h1 id="conclusion-and-outlook">Conclusion and outlook</h1><p>The revision to the OverviewView is more than a collection of individual improvements. It marks a structural change that fundamentally reshapes the interaction among search, filtering, data updates, and user interaction. From an initially heterogeneous interface with scattered responsibilities, a clearly modelled, consistent and technically robust view has emerged, whose behaviour is comprehensible and extensible in all essential dimensions.</p><p>A key result of this revision is the standardisation of the search logic. The introduction of a global search box, together with scope selection, forms a small, self-contained state machine that provides an intuitive entry point for the user. Complemented by the extended filter area, a flexible system is created that supports both fast search queries and more complex filter combinations – without competing with each other. The clear switch between advanced and straightforward mode prevents contradictory states and keeps cognitive effort low.</p><p>Equally important is the redesigned refresh architecture. With<code>safeRefresh()</code> and<code>the RefreshGuard</code>, a mechanism has been introduced to stabilise the application&rsquo;s refresh behaviour. Complex operations such as initialisation or reset are bundled into atomic, deterministic processes, while simple interactions can still react directly. This pattern operates in the background and is particularly noticeable to the user when the operation is quieter and less erratic.</p><p>The grid itself has also been further developed in terms of functionality and ergonomics. Copy functions, context-sensitive opening of details, dynamic flow badges and an improved column layout transform the table into an interactive workspace that not only provides information, but also enables action. This proximity between data and interaction reduces the need for additional dialogue changes, thereby contributing to a more fluid workflow.</p><p>The robustness of the view ultimately results from a variety of small but effective mechanisms: automatic synchronisation of filter fields, defensive evaluation of incomplete entries, clear prioritisation rules, and consistent logging. All these aspects ensure that the application remains reliable even under unusual input combinations and that the causes of errors can be traced if necessary.</p><p>This structural basis enables broad future expansion. The clear separation of UI logic, filter model, and refresh strategy provides a stable foundation on which subsequent features can be built without breaking points. Among other things, the following are conceivable:</p><p>– a server-side full-text search that extends the Simple/Advanced model, – colour or iconographic markings of other states such as &ldquo;soon to expire&rdquo;, – bulk actions for multiple selections, – a modular sorting and filtering pipeline, – tagging or labelling functions for short URLs, – advanced column settings or custom views.</p><p>The new OverviewView thus not only represents an improvement over the status quo but also marks a structural turning point. It creates clarity where implicit or scattered logic previously prevailed and establishes mechanisms that keep the system stable over the long term. In its entirety, the revision represents an important step towards a modern, scalable, and maintainable UI architecture that meets the requirements of growing use cases.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/PartII-Verision02.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/PartII-Verision02.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/PartII-Verision02.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - From Simple Search to Expert Mode: Advanced Filters and Synchronised Scopes for Power Users</title><link>https://svenruppert.com/posts/advent-calendar-2025-from-simple-search-to-expert-mode-advanced-filters-and-synchronised-scopes-for-power-users/</link><pubDate>Sat, 13 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-from-simple-search-to-expert-mode-advanced-filters-and-synchronised-scopes-for-power-users/</guid><description>Since its inception, the URL shortener&rsquo;s continuous development has focused on two core goals: a robust technical foundation without external frameworks and a modern, productive user interface that is both intuitive and efficient for power users. As part of the current development stage, an essential UI module has been revised – the OverviewView, i.e. the view in which users search, filter and manage all saved shortenings.</description><content:encoded>&lt;![CDATA[<p>Since its inception, the URL shortener&rsquo;s continuous development has focused on two core goals: a robust technical foundation without external frameworks and a modern, productive user interface that is both intuitive and efficient for power users. As part of the current development stage, an essential UI module has been revised – the OverviewView, i.e. the view in which users search, filter and manage all saved shortenings.</p><p>In the previous version, it became increasingly clear that the search and filtering components were decoupled. The basic search offered limited functionality, while the advanced filters were present but not integrated into a real control flow. In addition, the interaction between user actions such as reset, filter changes, paging, or opening the detail view was not sufficiently stabilised, which sometimes led to multiple refreshes or contradictory UI states.</p><p>The revision now implemented aims to achieve several structural objectives. The central focus was on standardising interactions: the search function was redesigned and integrated into a unified concept, including a global search bar with a clearly defined scope and a structured area for advanced filters. In parallel, the technical architecture of the refresh mechanisms has been revised to deliver a quieter, more reliable user experience.</p><p>This introduction first outlines the rationale for the changes and situates them within the broader development context. In the following chapters, the new global search is first described, followed by the synchronisation mechanisms, extended filters, internal refresh architecture, and grid improvements. The goal is not only a functional description but also a technical analysis of the mechanisms that enable these improvements and underpin future expansions.</p><p><strong>The source code for this development step can be found on GitHub</strong></p><p><strong>and can be found here:</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-07">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-07</a></p><h2 id="the-new-global-search">The new global search</h2><p>The introduction of global search marks a central step towards a consistent yet flexible operating logic within the OverviewView. While in the previous version the search function consisted of several independent elements, a uniform interaction surface has now been created, whose behaviour is clearly defined and technically cleanly modelled. The basis is a single text field, supplemented by a search-area selection, which eliminates the prior separation between URL and shortcode searches.</p><figure><img src="/images/2025/12/image-37-1024x502.png" alt="Screenshot of the URL Shortener Overview page displaying a search bar, options for filtering search results, and a table showcasing shortened URLs, their original URLs, creation dates, and expiration status." loading="lazy" decoding="async"/><p>In the source code, this standardisation is first reflected in the explicit introduction of two central UI components, which are declared as fixed components of the view:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private final TextField globalSearch = new TextField();</span></span><span class="line"><span class="cl">private final ComboBox<span class="nt">&lt;String&gt;</span> searchScope = new ComboBox<span class="err">&lt;</span>&gt;("Search in");</span></span><span class="line"><span class="cl">private final Button advancedBtn = new Button("Advanced filters", new Icon(VaadinIcon.SLIDERS));</span></span></code></pre></div></div><p>This clearly defines that there is precisely one global search field and exactly one selection for the search area. Both components are created early in the view&rsquo;s life cycle and are therefore available to all subsequent configuration steps. The actual design is done in the<code>buildSearchBar() method</code>, in which placeholders, width, and interaction behavior are specifically defined:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Component</span><span class="w"/><span class="nf">buildSearchBar</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setPlaceholder</span><span class="p">(</span><span class="s">"Search all..."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setClearButtonVisible</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setWidth</span><span class="p">(</span><span class="s">"28rem"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setValueChangeMode</span><span class="p">(</span><span class="n">LAZY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setValueChangeTimeout</span><span class="p">(</span><span class="n">VALUE_CHANGE_TIMEOUT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">searchScope</span><span class="p">.</span><span class="na">setItems</span><span class="p">(</span><span class="s">"URL"</span><span class="p">,</span><span class="w"/><span class="s">"Shortcode"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">searchScope</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"URL"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">searchScope</span><span class="p">.</span><span class="na">setWidth</span><span class="p">(</span><span class="s">"11rem"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pageSize</span><span class="p">.</span><span class="na">setMin</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pageSize</span><span class="p">.</span><span class="na">setMax</span><span class="p">(</span><span class="n">500</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pageSize</span><span class="p">.</span><span class="na">setStepButtonsVisible</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pageSize</span><span class="p">.</span><span class="na">setWidth</span><span class="p">(</span><span class="s">"140px"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// ...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The global search is not treated as an arbitrary text field, but is deliberately modelled as a central control element. The placeholder &ldquo;Search all&hellip;&rdquo; makes it clear that, regardless of the specific search area, the user initially enters only one search term. The actual routing of this value into the appropriate technical field handles the logic associated with the search field and scope selection. Choosing LAZY mode, combined with an explicit timeout, ensures that server-side filter requests are triggered only when the user has completed input.</p><figure><img src="/images/2025/12/image-35-1024x525.png" alt="URL shortener overview interface with a search bar, option to search by URL or shortcode, and a table displaying shortcodes with their corresponding URLs, created dates, expiration statuses, and action buttons." loading="lazy" decoding="async"/><p>The search box serves as the starting point for all queries. As soon as the user enters, the content is assigned to either the URL or the shortcode component of the filter model, depending on the currently selected search area. This assignment is not only a UI-side mechanism but also a clear rule within the internal logic: the value of the global search field is always bound to exactly one of the two fields in the request object. The central implementation of this coupling is carried out via the<code>ValueChangeListener</code> of the global search field:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">globalSearch</span><span class="p">.</span><span class="n">addValueChangeListener</span><span class="p">(</span><span class="n">e</span><span class="o">-&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">v</span><span class="p">=</span><span class="nc">Optional</span><span class="p">.</span><span class="n">ofNullable</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="n">getValue</span><span class="p">()).</span><span class="n">orElse</span><span class="p">(</span><span class="s2">""</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">searchScope</span><span class="p">.</span><span class="n">getValue</span><span class="p">().</span><span class="n">equals</span><span class="p">(</span><span class="s2">"Shortcode"</span><span class="p">))</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="n">v</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="n">v</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Here, each new value is first converted to a safe, non-null variant. The value of the ComboBox<code>searchScope</code>then decides<code/>whether the search term is interpreted as a shortcode filter (<code>codePart</code>) or a URL filter (<code>urlPart</code>). Only one of the two fields may be occupied at a time; the other is cleared automatically. This avoids ambiguous search situations in which multiple filters are applied simultaneously and uncoordinatedly. The global search field thus becomes the sole source for exactly one specific logical filter state.</p><p>Another major innovation is the tight coupling between the search field and the search area. The selection of the range – URL or shortcode – directly determines which part of the filter model is active. To ensure that this relationship remains consistent in both directions, the scope selection also reacts to changes and reflects the current search value in the appropriate field:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="n">searchScope</span><span class="p">.</span><span class="n">addValueChangeListener</span><span class="p">(</span><span class="n">_</span><span class="o">-&gt;</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">v</span><span class="p">=</span><span class="nc">Optional</span><span class="p">.</span><span class="n">ofNullable</span><span class="p">(</span><span class="n">globalSearch</span><span class="p">.</span><span class="n">getValue</span><span class="p">()).</span><span class="n">orElse</span><span class="p">(</span><span class="s2">""</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="s2">"Shortcode"</span><span class="p">.</span><span class="n">equals</span><span class="p">(</span><span class="n">searchScope</span><span class="p">.</span><span class="n">getValue</span><span class="p">()))</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="n">v</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="k">else</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="n">v</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>While the text field listener reacts when the search term changes, it is responsible for maintaining consistent search state when the user subsequently changes the search scope. Both implementations follow the same pattern: a standard source value is interpreted as either a shortcode or a URL, and the inactive field is consistently emptied. This keeps the search interface not only visually clear, but also logical.</p><p>The behaviour of the global search is also designed to enable clear prioritisation when combined with the advanced filters. As long as the advanced filters are closed, the global search box controls the filter state independently. When the user opens the advanced area, the global search loses its active role and is relegated to the background, both visually and technically. This separation is encapsulated by a small helper method that explicitly determines the state in which the simple search may be:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setSimpleSearchEnabled</span><span class="p">(</span><span class="kt">boolean</span><span class="w"/><span class="n">enabled</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="n">enabled</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">searchScope</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="n">enabled</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">resetBtn</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setHelperText</span><span class="p">(</span><span class="n">enabled</span><span class="w"/><span class="o">?</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"Disabled while Advanced filters are open"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This method not only controls the activation and deactivation of the input elements but also provides context-sensitive help text that explains why global search is unavailable in open Advanced mode. This prevents two parallel filter sources from competing with each other and destabilizing the overall state. At the same time, the operating concept remains transparent, as the UI clearly communicates its status.</p><p>With this revamped global search, an intuitive, clear entry point has been created with a well-defined function from both the user and technical architecture perspectives. The search field, the scope selection, and the associated filter fields form a small, self-contained state machine whose behaviour is explicitly modelled in the code. The following chapters now examine how this search interacts with the other components and how the underlying synchronisation logic ensures consistent state transitions.</p><h2 id="search-scopes-and-synchronisation-logic">Search scopes and synchronisation logic</h2><p>While the global search offers a clear entry point, its real strength only becomes apparent through interaction with the underlying synchronisation logic. It is crucial that the selected search scope – i.e. the decision between URL and shortcode – does not remain just a visual interface detail, but is consistently transferred to the internal state model. The goal of this layer is to ensure that at any given time, it is clear which filter is active and which parts of the UI represent that filter.</p><p>From the user&rsquo;s perspective, the behaviour can be divided into two central scenarios. In simple mode, the combination of the global search field and scope selection directly controls the filter state. The user implicitly determines, via the input context, whether to search for a target URL or a shortcode. In advanced mode, by contrast, the global search is reversed, and the detail fields fully control the filter state. This transition between modes is the core of synchronisation logic.</p><figure><img src="/images/2025/12/image-36-1024x946.png" alt="Overview of the URL shortener interface, featuring a search bar, advanced filters for shortcode and original URL, and a table displaying shortcodes, URLs, creation dates, expiration status, and action buttons." loading="lazy" decoding="async"/><p>From a technical standpoint, this logic is based on a few, clearly defined principles. First, there is exactly one source for the effective search string at any given time. In simple mode, this is the global search field, which is mapped to either the URL or the shortcode component of the filter model, depending on the scope. In advanced mode, the dedicated URL and shortcode fields in the Advanced area are transferred directly to the request object. Second, there must be no competing states: if the user is working in Advanced mode, global search items are disabled; when the user returns to simple mode, the state is derived from the previous detail values.</p><p>The technical implementation of this switch begins where the View establishes the Advanced area as a controlling element. Central is the listener, which reacts to the opening and closing of the<code>details</code> container:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">advanced</span><span class="p">.</span><span class="na">addOpenedChangeListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">nowClosed</span><span class="w"/><span class="o">=</span><span class="w"/><span class="o">!</span><span class="n">ev</span><span class="p">.</span><span class="na">isOpened</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">nowClosed</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">applyAdvancedToSimpleAndReset</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setSimpleSearchEnabled</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>These few lines model the entire state change between the modes. When the user opens the Advanced section, the simple search is disabled. When closing, not only is the Advanced area collapsed, but a consolidation step is also triggered via<code>applyAdvancedToSimpleAndReset(),</code> which converts the previous detailed configuration into a simple, global search state.</p><p>To ensure that disabling the simple search does not lead to an inconsistent UI impression, the View encapsulates the necessary adjustments in a small helper method:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setSimpleSearchEnabled</span><span class="p">(</span><span class="kt">boolean</span><span class="w"/><span class="n">enabled</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="n">enabled</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">searchScope</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="n">enabled</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">resetBtn</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setHelperText</span><span class="p">(</span><span class="n">enabled</span><span class="w"/><span class="o">?</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"Disabled while Advanced filters are open"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The method controls both the interactivity of the search field and scope selection as well as the accompanying help text. Once the Advanced section is opened, the Basic Search values are retained, but cannot be changed. At the same time, the helper text makes it clear that global search is currently disabled. At this level, the first part of the principle described above is implemented: There is always only one active source that determines the effective filter state.</p><p>The opposite direction – from advanced mode back to simple mode – is more complex because a choice has to be made here. In the Advanced area, a shortcode fragment and a URL fragment can be entered at the same time. Both would be suitable as filters but cannot be readily combined into a single global search field. This is precisely where<code>applyAdvancedToSimpleAndReset()</code> comes in:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">applyAdvancedToSimpleAndReset</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">codePart</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="s">""</span><span class="p">).</span><span class="na">trim</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">url</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">urlPart</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="s">""</span><span class="p">).</span><span class="na">trim</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">hasCode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="o">!</span><span class="n">code</span><span class="p">.</span><span class="na">isBlank</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">hasUrl</span><span class="w"/><span class="o">=</span><span class="w"/><span class="o">!</span><span class="n">url</span><span class="p">.</span><span class="na">isBlank</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">winnerValue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">hasCode</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">code</span><span class="w"/><span class="p">:</span><span class="w"/><span class="p">(</span><span class="n">hasUrl</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">url</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">winnerScope</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">hasCode</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"Shortcode"</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"URL"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="kd">var</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RefreshGuard</span><span class="p">(</span><span class="kc">true</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">codePart</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">codeCase</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">urlPart</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">urlCase</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fromDate</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fromTime</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">toDate</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">toTime</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">sortBy</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dir</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">sortBy</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"createdAt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dir</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"desc"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">searchScope</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="n">winnerScope</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">winnerValue</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="n">winnerValue</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setSimpleSearchEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">globalSearch</span><span class="p">.</span><span class="na">focus</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The method begins by evaluating the codePart and<code>urlPart</code> detail fields. Both values are defensively converted to strings and then checked for non-empty content. Two things are derived from this: a &ldquo;winner&rdquo; value and a &ldquo;winner&rdquo; scope. If a shortcode fragment is set, it takes precedence over any URL fragment. Only if there is no shortcode and only a URL is the URL considered a winner. If both are empty, an empty search string is used, and the scope is reset to &ldquo;URL&rdquo;. In this way, the prioritisation described in the running text is implemented in practice, without ambiguity.</p><p>In the second block of the method, all Advanced fields are consistently reset. In addition to the text fields for shortcodes and URLs, this applies to the case-sensitivity checkboxes and the time-slot and sorting fields. The Advanced range is thus returned to a defined initial state. Thanks to the<code>RefreshGuard, this reset does not occur through multiple individual refreshes; instead, it is treated as an aggregated state change that culminate</code>s in a controlled reload.</p><p>Only then is the previously determined winner state reflected into the simple search. The global search scope is set to winnerScope; the global search string is either filled with winnerValue or left empty. Finally, the simple search is reactivated, and the focus is set to the global search field. This provides the user with a straightforward, reduced interface after closing the Advanced area, reflecting the active filter state derived from the previously selected detail values.</p><p>Overall, this yields a small but precise state machine. Opening the Advanced pane shifts control entirely to the detail fields and visibly disables global search. Closing triggers a controlled reduction to a single, easy-to-understand filter state. The synchronisation logic remains fully comprehensible in the code, is encapsulated in a few clearly structured methods, and can be extended with additional filter fields if necessary without violating the basic principle. On this basis, the following chapters can now examine other aspects of filtering in detail, such as the extended filter fields and the refresh architecture.</p><h2 id="advanced-filters-conception-and-ui-design">Advanced Filters: Conception and UI Design</h2><p>The global search serves as a compact entry point to the OverviewView&rsquo;s filter logic. However, their range of functions is deliberately limited to ensure a low barrier to entry and rapid operation. As soon as the requirements go beyond a simple text fragment, this model reaches its natural limits. This is where Advanced Filters come into play, giving users much finer control over short URL filtering while providing a structured, visually comprehensible interface.</p><p>Conceptually, the Advanced area was designed as a deliberately separate mode. It should not be understood as a mere extension of the existing search field, but rather as an independent filter context that becomes active only when the user explicitly opens it. This decision takes into account two considerations. On the one hand, the simple mode should not be burdened with options unnecessary in many everyday scenarios. On the other hand, advanced filter operations – such as the combination of a shortcode fragment, a URL substring, a time window, and sorting – should have a clearly identifiable workspace in which all associated input elements are spatially bundled.</p><p>In the source code, this concept already begins at the field level, where the components for the Advanced area are clearly declared separate from the global search:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private final TextField codePart = new TextField("Shortcode contains");</span></span><span class="line"><span class="cl">private final Checkbox codeCase = new Checkbox("Case-sensitive");</span></span><span class="line"><span class="cl">private final TextField urlPart = new TextField("Original URL contains");</span></span><span class="line"><span class="cl">private final Checkbox urlCase = new Checkbox("Case-sensitive");</span></span><span class="line"><span class="cl">private final DatePicker fromDate = new DatePicker("From (local)");</span></span><span class="line"><span class="cl">private final TimePicker fromTime = new TimePicker("Time");</span></span><span class="line"><span class="cl">private final DatePicker toDate = new DatePicker("To (local)");</span></span><span class="line"><span class="cl">private final TimePicker toTime = new TimePicker("Time");</span></span><span class="line"><span class="cl">private final ComboBox<span class="nt">&lt;String&gt;</span> sortBy = new ComboBox<span class="err">&lt;</span>&gt;("Sort by");</span></span><span class="line"><span class="cl">private final ComboBox<span class="nt">&lt;String&gt;</span>dir = new ComboBox<span class="err">&lt;</span>&gt;("Direction");</span></span></code></pre></div></div><p>These fields define the semantic dimensions of the advanced filters: textual filtering via shortcode and original URL (optional), an explicit time window, and sorting criteria. The fact that they are listed as separate attributes of the view expresses the separate mode described above: they do not belong to the global search but to an individual, extended view of the data space.</p><p>From a UI perspective, this separation manifests as a details container that makes the advanced filters collapsible. When closed, the Advanced section does not occupy any additional space and only indicates, via its header, that more options are available. Only when opened does a structured form unfold, which arranges the various filter dimensions into logically related groups. The concrete design begins with the configuration of the fields themselves:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="na">setPlaceholder</span><span class="p">(</span><span class="s">"e.g. ex-"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="na">setValueChangeMode</span><span class="p">(</span><span class="n">LAZY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="na">setValueChangeTimeout</span><span class="p">(</span><span class="n">VALUE_CHANGE_TIMEOUT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">safeRefresh</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="na">setPlaceholder</span><span class="p">(</span><span class="s">"e.g. docs"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="na">setValueChangeMode</span><span class="p">(</span><span class="n">LAZY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="na">setValueChangeTimeout</span><span class="p">(</span><span class="n">VALUE_CHANGE_TIMEOUT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">safeRefresh</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">sortBy</span><span class="p">.</span><span class="na">setItems</span><span class="p">(</span><span class="s">"createdAt"</span><span class="p">,</span><span class="w"/><span class="s">"shortCode"</span><span class="p">,</span><span class="w"/><span class="s">"originalUrl"</span><span class="p">,</span><span class="w"/><span class="s">"expiresAt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dir</span><span class="p">.</span><span class="na">setItems</span><span class="p">(</span><span class="s">"asc"</span><span class="p">,</span><span class="w"/><span class="s">"desc"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">fromDate</span><span class="p">.</span><span class="na">setClearButtonVisible</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">toDate</span><span class="p">.</span><span class="na">setClearButtonVisible</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">fromTime</span><span class="p">.</span><span class="na">setStep</span><span class="p">(</span><span class="n">Duration</span><span class="p">.</span><span class="na">ofMinutes</span><span class="p">(</span><span class="n">15</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">toTime</span><span class="p">.</span><span class="na">setStep</span><span class="p">(</span><span class="n">Duration</span><span class="p">.</span><span class="na">ofMinutes</span><span class="p">(</span><span class="n">15</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">fromTime</span><span class="p">.</span><span class="na">setPlaceholder</span><span class="p">(</span><span class="s">"hh:mm"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">toTime</span><span class="p">.</span><span class="na">setPlaceholder</span><span class="p">(</span><span class="s">"hh:mm"</span><span class="p">);</span></span></span></code></pre></div></div><p>The text fields for the shortcode and URL provide meaningful placeholders and, like the global search, use a lazy ValueChange mode with a timeout. This prevents a new filter run from being triggered immediately with each input, while at the same time the filters respond quickly to changes. The sorting pair<code>sortBy</code>is preassigned to you, with permissible values, and thus is embedded within a defined space of possible sorting strategies. Convenience functions supplement the date and time fields: Clear buttons, fixed 15-minute time grids, and placeholders for the time format help users enter data while reducing the risk of invalid values.</p><p>The spatial structure of Advanced Filters is designed to visually group related information. Instead of placing all components on a single long line, the implementation supports multiple upstream layouts. First, the date and time fields are merged into two groups:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">fromGroup</span><span class="p">=</span><span class="n">new</span><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">fromDate</span><span class="p">,</span><span class="n">fromTime</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">fromGroup</span><span class="p">.</span><span class="n">setDefaultVerticalComponentAlignment</span><span class="p">(</span><span class="nc">Alignment</span><span class="p">.</span><span class="n">END</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">toGroup</span><span class="p">=</span><span class="n">new</span><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">toDate</span><span class="p">,</span><span class="n">toTime</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">toGroup</span><span class="p">.</span><span class="n">setDefaultVerticalComponentAlignment</span><span class="p">(</span><span class="nc">Alignment</span><span class="p">.</span><span class="n">END</span><span class="p">);</span></span></span></code></pre></div></div><p>The two horizontal layouts, „fromGroup“ and „toGroup“, ensure that date and time visually appear as a coherent unit. The vertical alignment at the bottom creates a calm, uniform appearance, even when field heights vary slightly. These groups are then embedded in a FormLayout, which forms the actual responsive structure:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">FormLayout</span><span class="w"/><span class="n">searchBlock</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FormLayout</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">searchBlock</span><span class="p">.</span><span class="na">setWidthFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">searchBlock</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">codePart</span><span class="p">,</span><span class="w"/><span class="n">urlPart</span><span class="p">,</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">codeCase</span><span class="p">,</span><span class="w"/><span class="n">urlCase</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">searchBlock</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">fromGroup</span><span class="p">,</span><span class="w"/><span class="n">toGroup</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">searchBlock</span><span class="p">.</span><span class="na">setResponsiveSteps</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FormLayout</span><span class="p">.</span><span class="na">ResponsiveStep</span><span class="p">(</span><span class="s">"0"</span><span class="p">,</span><span class="w"/><span class="n">1</span><span class="p">),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FormLayout</span><span class="p">.</span><span class="na">ResponsiveStep</span><span class="p">(</span><span class="s">"32rem"</span><span class="p">,</span><span class="w"/><span class="n">2</span><span class="p">),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FormLayout</span><span class="p">.</span><span class="na">ResponsiveStep</span><span class="p">(</span><span class="s">"56rem"</span><span class="p">,</span><span class="w"/><span class="n">3</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">);</span></span></span></code></pre></div></div><p>Here, the textual filters – shortcode and URL – as well as the associated case sensitivity checkboxes are arranged together in a block. Below this are the groups for the time slot. ResponsiveSteps determines how many columns the layout may use at different widths. Below 32 rem, a single-column layout is selected; at medium width, two columns are available; and above 56 rem, the layout can be extended to three columns. In this way, the input mask remains readable and well-structured across wide desktop views and narrower windows or split screens.</p><p>The sorting control is deliberately visually decoupled from the search block, but positioned on the same horizontal axis. For this purpose, a separate toolbar area will be set up:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">sortBy</span><span class="p">.</span><span class="na">setLabel</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">sortBy</span><span class="p">.</span><span class="na">setPlaceholder</span><span class="p">(</span><span class="s">"Sort by"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">sortBy</span><span class="p">.</span><span class="na">setWidth</span><span class="p">(</span><span class="s">"12rem"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dir</span><span class="p">.</span><span class="na">setLabel</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dir</span><span class="p">.</span><span class="na">setPlaceholder</span><span class="p">(</span><span class="s">"Direction"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dir</span><span class="p">.</span><span class="na">setWidth</span><span class="p">(</span><span class="s">"8rem"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">HorizontalLayout</span><span class="w"/><span class="n">sortToolbar</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">sortBy</span><span class="p">,</span><span class="w"/><span class="n">dir</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">sortToolbar</span><span class="p">.</span><span class="na">setAlignItems</span><span class="p">(</span><span class="n">FlexComponent</span><span class="p">.</span><span class="na">Alignment</span><span class="p">.</span><span class="na">END</span><span class="p">);</span></span></span></code></pre></div></div><p>By removing the ComboBox labels and using placeholders, the interface remains compact without sacrificing intelligibility. The fixed width ensures stable alignment, while the horizontal grouping makes it clear that both fields functionally belong together. The alignment at the bottom blends with the rest of the header, where the filter boxes are also aligned on a common baseline.</p><p>Finally, the search block and sort bar are merged into a single header layout that constitutes the visible content of the Advanced area. This header layout is then embedded in a<code>Details</code> component:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">HorizontalLayout</span><span class="w"/><span class="n">advHeader</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">searchBlock</span><span class="p">,</span><span class="w"/><span class="n">sortToolbar</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">advHeader</span><span class="p">.</span><span class="na">setWidthFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">advHeader</span><span class="p">.</span><span class="na">setSpacing</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">advHeader</span><span class="p">.</span><span class="na">setAlignItems</span><span class="p">(</span><span class="n">FlexComponent</span><span class="p">.</span><span class="na">Alignment</span><span class="p">.</span><span class="na">START</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">advHeader</span><span class="p">.</span><span class="na">expand</span><span class="p">(</span><span class="n">searchBlock</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">advHeader</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"flex-wrap"</span><span class="p">,</span><span class="w"/><span class="s">"wrap"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">advHeader</span><span class="p">.</span><span class="na">setVerticalComponentAlignment</span><span class="p">(</span><span class="n">FlexComponent</span><span class="p">.</span><span class="na">Alignment</span><span class="p">.</span><span class="na">END</span><span class="p">,</span><span class="w"/><span class="n">sortToolbar</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">advanced</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Details</span><span class="p">(</span><span class="s">"Advanced filters"</span><span class="p">,</span><span class="w"/><span class="n">advHeader</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">advanced</span><span class="p">.</span><span class="na">setOpened</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">advanced</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">getThemeList</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"filled"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">setSimpleSearchEnabled</span><span class="p">(</span><span class="o">!</span><span class="n">advanced</span><span class="p">.</span><span class="na">isOpened</span><span class="p">());</span></span></span></code></pre></div></div><p>The<code>advHeader</code> ensures the search block occupies the available space, while the sorting tools are anchored to the right edge. Enabling<code>flex-wrap</code> allows the header to wrap to multiple lines within a limited width without disrupting the logical proximity of the elements. The<code>Details</code> component includes this header and makes the entire Advanced section collapsible. When closed, only the title &ldquo;Advanced filters&rdquo; remains visible; when opened, the complete form unfolds. The application of the filled theme also provides the area with a visual demarcation from the surrounding layout.</p><p>In terms of content, the Advanced Filters design prioritises making the essential dimensions of the data directly accessible. Shortcodes and destination URLs serve as textual entry points, and the search can be configured as case-sensitive or case-insensitive. The temporal context of the short URL – such as the creation or expiration interval considered – is mapped via combined date and time fields that explicitly work in the user&rsquo;s local context. Finally, the sort field and sorting direction provide fine control over the order of displayed entries, enabling newly created or soon-expiring links to be brought to the foreground.</p><p>This expanded range of functions should not leave the user confronted with a confusing number of control elements. The precise spatial separation within the hinged container, the well-thought-out grouping of the fields, and the responsive arrangement therefore not only provide an aesthetic but, above all, a cognitive relief. The user can choose whether to use the standard global search options or switch to expert mode, which provides more detailed control over the database.</p><p>On this basis, the Advanced area can be seamlessly embedded into the rest of the architecture in the following chapters. In particular, the connection between the synchronisation logic and the refresh architecture described above demonstrates how the UI design and the internal state engine work in concert to keep even more complex filter requests stable and easy to understand.</p><p>Cheer Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Version-03-Green.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Version-03-Green.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Version-03-Green.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - Introduction of multiple aliases - Part 2</title><link>https://svenruppert.com/posts/advent-calendar-2025-introduction-of-multiple-aliases-part-2/</link><pubDate>Fri, 12 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-introduction-of-multiple-aliases-part-2/</guid><description>Today&rsquo;s update introduces another practical development step for the URL shortener. After the last few days were dedicated to UI refinement and the better structuring of detailed dialogues and form logic, the focus is now on an aspect that plays a significant role in the everyday life of many users: the flexible management of multiple aliases per target URL.</description><content:encoded>&lt;![CDATA[<p>Today&rsquo;s update introduces another practical development step for the URL shortener. After the last few days were dedicated to UI refinement and the better structuring of detailed dialogues and form logic, the focus is now on an aspect that plays a significant role in the everyday life of many users: the flexible management of multiple aliases per target URL.</p><p><strong>You can find the source code for this development status on GitHub under</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-06">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-06</a></p><p>Here are the relevant screenshots of the Vaadin application.</p><p><figure><img src="/images/2025/12/image-29-1024x776.png" alt="Overview of the URL shortener application, displaying a list of created shortcodes, original URLs, and their expiration details." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-30-1024x627.png" alt="Screenshot of a new alias management window in a URL shortener application, displaying fields for entering aliases, a preview section showing generated short URLs, and validation status indicators for each alias." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-31-1024x321.png" alt="Screenshot of the URL shortener application interface, featuring sections for creating new short links, managing aliases, and displaying their status." loading="lazy" decoding="async"/></p><p>What was previously only possible sequentially – an alias, a destination address – will now become a dynamic workspace. Users can add new aliases to existing short links, create variants for different use cases, or manage parallel campaigns without creating new records each time. The MultiAliasEditorStrict forms the core of this new workflow: inline validation, fast feedback, clear status indicators and an event flow that propagates changes directly into the OverviewView. The application responds immediately to user input and keeps the entire interface consistent, without requiring manual reloads or context switching.</p><p>But this is much more than just a UI comfort update. The increased flexibility in the alias structure inevitably leads to higher demands on server-side stability – especially on the stability of forwarders. Once several aliases point to the same destination address, the redirect logic must be deterministic and reliably detect erroneous requests. Therefore, a consistent, centralised request check was introduced in parallel with the UI extension to secure all forwarding operations and ensure predictable HTTP behaviour.</p><p>These changes combine two sides of the same coin: greater user freedom in their daily work and a robust technical foundation that reliably supports it. In the following, we will therefore take a closer look at the revised redirect implementation and the new security mechanisms that underpin stable, traceable, and maintainable redirect behaviour.</p><h2 id="more-robust-redirects-and-http-security">More Robust Redirects and HTTP Security</h2><p>In parallel with the extensive improvements to the user interface, the server-side component of the URL shortener has also been revised. A central focus was on increasing the stability and security of HTTP communication. Especially when handling redirects, several methods have had to carry out similar checks – for example, whether the correct HTTP method was used or whether a request was incomplete. These repetitions led to inconsistent behaviour and made maintenance difficult.</p><p>In the process, a consistent request-handling system was introduced to centralise these checks. The code for the RedirectHandler clearly shows how a robust redirect is now implemented:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.urlshortener.server.handler;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">import com.svenruppert.urlshortener.core.http.HttpStatus;</span></span><span class="line"><span class="cl">import com.svenruppert.urlshortener.core.store.UrlMappingStore;</span></span><span class="line"><span class="cl">import com.svenruppert.urlshortener.server.util.RequestMethodUtils;</span></span><span class="line"><span class="cl">import com.svenruppert.urlshortener.server.util.ResponseWriter;</span></span><span class="line"><span class="cl">import com.sun.net.httpserver.HttpExchange;</span></span><span class="line"><span class="cl">import java.io.IOException;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public class RedirectHandler</span></span><span class="line"><span class="cl"> implements HttpHandler, HasLogger {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private final UrlMappingLookup store;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public RedirectHandler(UrlMappingLookup store) {</span></span><span class="line"><span class="cl"> this.store = store;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void handle(HttpExchange exchange)</span></span><span class="line"><span class="cl"> throws IOException {</span></span><span class="line"><span class="cl"> if (! RequestMethodUtils.requireGet(exchange)) return;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> final String path = exchange.getRequestURI().getPath(); e.g. "/ABC123"</span></span><span class="line"><span class="cl"> if (path == null || !path.startsWith(PATH_REDIRECT)) {</span></span><span class="line"><span class="cl"> exchange.sendResponseHeaders(400, -1);</span></span><span class="line"><span class="cl"> return;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> final String code = path.substring((PATH_REDIRECT).length());</span></span><span class="line"><span class="cl"> if (code.isBlank()) {</span></span><span class="line"><span class="cl"> exchange.sendResponseHeaders(400, -1);</span></span><span class="line"><span class="cl"> return;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> logger().info("looking for short code {}", code);</span></span><span class="line"><span class="cl"> Optional<span class="nt">&lt;String&gt;</span> target = store</span></span><span class="line"><span class="cl"> .findByShortCode(code)</span></span><span class="line"><span class="cl"> .map(ShortUrlMapping::originalUrl);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> if (target.isPresent()) {</span></span><span class="line"><span class="cl"> exchange.getResponseHeaders().add("Location", target.get());</span></span><span class="line"><span class="cl"> exchange.sendResponseHeaders(302, -1);</span></span><span class="line"><span class="cl"> } else {</span></span><span class="line"><span class="cl"> exchange.sendResponseHeaders(404, -1);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The RedirectHandler class ensures that only valid GET requests are accepted. Invalid methods – such as POST or DELETE – are caught by the RequestMethodUtils helper class. This prevents inadvertent writes to a read-only resource.</p><p>The check logic in RequestMethodUtils is deliberately kept simple and centralises all recurring security checks. The following excerpt shows how she works internally:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.svenruppert.urlshortener.server.util</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.svenruppert.urlshortener.core.http.HttpStatus</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.sun.net.httpserver.HttpExchange</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">RequestMethodUtils</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">requireGet</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">exchange</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">HasLogger</span><span class="p">.</span><span class="na">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"handle ... {} "</span><span class="p">,</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestMethod</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNull</span><span class="p">(</span><span class="n">exchange</span><span class="p">,</span><span class="w"/><span class="s">"exchange"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">" GET"</span><span class="p">.</span><span class="na">equalsIgnoreCase</span><span class="p">(</span><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestMethod</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">writeJson</span><span class="p">(</span><span class="n">exchange</span><span class="p">,</span><span class="w"/><span class="n">METHOD_NOT_ALLOWED</span><span class="p">,</span><span class="w"/><span class="s">"Only GET is allowed for this endpoint."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SNIP</span><span class="p">...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This helper class serves as a compact security wall: every request-processing method calls it at the beginning. This creates predictable, consistent behaviour across the HTTP stack. This is particularly relevant for forwarding, as unauthorised or erroneous methods are reliably blocked.</p><p>Response generation is handled by ResponseWriter, which handles clean header configuration and uniform error output. In contrast to earlier implementations, which manually generated JSON or text responses in several places, there is now a central method:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">writeJson</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">ex</span><span class="p">,</span><span class="w"/><span class="n">HttpStatus</span><span class="w"/><span class="n">httpStatus</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">message</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">HasLogger</span><span class="p">.</span><span class="na">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"writeJson {}, {}"</span><span class="p">,</span><span class="w"/><span class="n">httpStatus</span><span class="p">,</span><span class="w"/><span class="n">message</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">message</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(</span><span class="n">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Headers</span><span class="w"/><span class="n">h</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">getResponseHeaders</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">h</span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="n">CONTENT_TYPE</span><span class="p">,</span><span class="w"/><span class="n">JSON_CONTENT_TYPE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">httpStatus</span><span class="p">.</span><span class="na">code</span><span class="p">(),</span><span class="w"/><span class="n">data</span><span class="p">.</span><span class="na">length</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">getResponseBody</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">HasLogger</span><span class="p">.</span><span class="na">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"writeJson {} (catch)"</span><span class="p">,</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="s">"{\"error\":\""</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"\"}"</span><span class="p">).</span><span class="na">getBytes</span><span class="p">(</span><span class="n">StandardCharsets</span><span class="p">.</span><span class="na">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">getResponseHeaders</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="n">CONTENT_TYPE</span><span class="p">,</span><span class="w"/><span class="n">JSON_CONTENT_TYPE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">INTERNAL_SERVER_ERROR</span><span class="p">.</span><span class="na">code</span><span class="p">(),</span><span class="w"/><span class="n">body</span><span class="p">.</span><span class="na">length</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">getResponseBody</span><span class="p">().</span><span class="na">write</span><span class="p">(</span><span class="n">body</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">ignoredI</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">HasLogger</span><span class="p">.</span><span class="na">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"writeJson (catch - ignored I) {} "</span><span class="p">,</span><span class="w"/><span class="n">ignoredI</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">ignoredII</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">HasLogger</span><span class="p">.</span><span class="na">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"writeJson (catch - ignored II) {} "</span><span class="p">,</span><span class="w"/><span class="n">ignoredII</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The merging of these helper classes yields a well-structured server architecture. Each handler is leaner, easier to read, and easier to test. For users, this results in a more reliable redirect: error messages are displayed consistently, HTTP status codes match the expected behaviour exactly, and unauthorised methods do not cause side effects.</p><p>This also gives the URL shortener additional robustness at the protocol level. Error handling is traceable, behaviour is transparent, and server components follow the same principles of clarity and reusability introduced in the UI. In this way, the system&rsquo;s technical integrity is strengthened, as is user confidence in its stability.</p><h2 id="conclusion-user-comfort-meets-clean-design">Conclusion: User Comfort Meets Clean Design</h2><p>With the completion of this development step, the URL shortener has developed into a much more mature system – both technically and conceptually. What started as a simple platform for creating and managing individual shortlinks has become a full-fledged application that balances user needs, security, and maintainability. The transition from a one-alias logic to a flexible multi-alias approach is probably the most visible progress. Still, the real strength of this update lies in the design&rsquo;s consistent coherence.</p><p>From the user&rsquo;s perspective, a tool is now available that makes workflows more fluid, consistent, and traceable. The detail-dialogue and the Create View form a harmonious pair: both use the same editor, the exact validation mechanisms and the same event flow. Actions are immediately visible, and changes are automatically displayed. The result is a surface that hides its technical complexity and instead focuses on efficiency and clarity.</p><p>A clear development is also noticeable on the architectural level. The interplay of Vaadin EventBus, component-based UI structure and centralised HTTP handling has resulted in a maintenance-friendly, extensible system. Every level – from the user interface to the validation logic to the server – follows the same principles: clear responsibilities, loose coupling, and transparent behaviour. This uniformity is evident not only in the code but above all in everyday use.</p><p>This step thus sets a clear benchmark for future project expansions. The concept of multiple aliases, the consistent reactivity of the UI, and the clean server design will not remain isolated features in the coming development phases; they will serve as a structural basis. Working on the user experience has shown that technical design and interaction quality are not mutually exclusive – on the contrary, they reinforce each other when they are consistently coordinated.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Part-Two-Green.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Part-Two-Green.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Part-Two-Green.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar 2025 - Introduction of multiple aliases - Part 1</title><link>https://svenruppert.com/posts/advent-calendar-2025-introduction-of-multiple-aliases-part-1/</link><pubDate>Thu, 11 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-introduction-of-multiple-aliases-part-1/</guid><description>Introduction: More convenience for users With today&rsquo;s development milestone for the URL Shortener project, a crucial improvement has been achieved, significantly enhancing the user experience. Up to this point, working with short URLs was functional, but in many ways it was still linear: each destination URL was assigned exactly one alias. This meant users had to create a new short URL for each context or campaign, even when the destination was identical. While this approach was simple, it wasn&rsquo;t sufficiently flexible to meet real-world application requirements.</description><content:encoded>&lt;![CDATA[<h2 id="introduction-more-convenience-for-users">Introduction: More convenience for users</h2><p>With today&rsquo;s development milestone for the URL Shortener project, a crucial improvement has been achieved, significantly enhancing the user experience. Up to this point, working with short URLs was functional, but in many ways it was still linear: each destination URL was assigned exactly one alias. This meant users had to create a new short URL for each context or campaign, even when the destination was identical. While this approach was simple, it wasn&rsquo;t sufficiently flexible to meet real-world application requirements.</p><p><strong>You can find the source code for this development status on Github under</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-06">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-06</a></p><figure><img src="/images/2025/12/image-26-1024x296.png" alt="Screenshot of the URL Shortener overview page showing a list of shortcodes, their corresponding URLs, expiration status, and action buttons for managing short links." loading="lazy" decoding="async"/><p>In everyday life, users quickly reach their limits when they want to map the same destination address for different purposes – for example, to analyse access from other sources or to separate internal and external uses. Working with only one alias per target URL forced them to work around and led to unnecessary redundancy in the stored mappings. Especially in professional environments where tracking, traceability, and reusability are essential, this situation was unsatisfactory.</p><p>With today&rsquo;s development, this restriction has been lifted. The system now allows multiple aliases to point to a common destination URL. This focuses on user convenience: Existing short URLs can now be extended with new aliases without creating a new dataset. The process is accompanied by an intuitive user interface that integrates seamlessly into the application&rsquo;s detailed dialogue.</p><p><figure><img src="/images/2025/12/image-24-1024x776.png" alt="Overview of a URL shortener application displaying shortcodes, original URLs, creation dates, expiration details, and actions for handling the URLs." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-23-1024x627.png" alt="A modal dialogue titled ’new alias for: sru’ showing a section for entering aliases with examples, a button to validate them, and a list of aliases with their previews and statuses." loading="lazy" decoding="async"/></p><p>This extension is more than just a functional improvement. It changes the way users interact with the system. Whereas previously each alias represented a separate entity, a central destination URL with variable identifiers is now introduced. The user no longer thinks in terms of individual links, but rather in terms of a flexible alias space mapped to a destination address. This reduces cognitive load and noticeably accelerates typical workflows.</p><figure><img src="/images/2025/12/image-27-1024x321.png" alt="A user interface for a URL shortener application, showcasing a form to create new short links with fields for target URL, expiry date, and expiry time. On the right, a section displays a list of aliases with their previews and validation statuses." loading="lazy" decoding="async"/><p>On a technical level, this development was supported by the introduction of new UI components and event mechanisms that enable dynamic synchronisation between the detail dialogue and the overview. But the real meaning lies in the user experience itself: the shortener becomes a tool that adapts to real-world usage patterns – not the other way around.</p><h2 id="from-single-alias-to-multi-alias-management">From single alias to multi-alias management</h2><p>The introduction of multi-alias management marks a clear turning point in the URL shortener&rsquo;s development. While previously each short URL was inseparably bound to a single alias, the system now supports a more flexible, yet more natural, assignment model. From a technical standpoint, this means a structural expansion of the data model, but from the user&rsquo;s perspective, it means one thing above all: freedom to manage one&rsquo;s own short-link landscape.</p><figure><img src="/images/2025/12/image-25-1024x588.png" alt="An interface for managing URL aliases, featuring fields for entering aliases, a preview section, and buttons for taking over or validating the aliases. The status of each alias is displayed as valid." loading="lazy" decoding="async"/><p>At the heart of this innovation is the MultiAliasEditorStrict component, designed to intuitively guide users through managing multiple aliases. The editor lets you add new aliases, modify existing ones, or remove erroneous entries without opening modal dialogues or new dialogues. Instead, the user remains in a consistent context – the detailed view of a target URL – and can act directly there. This decision was deliberate to avoid friction in the interaction and maintain the flow between viewing and editing.</p><p>The implementation of MultiAliasEditorStrict follows a clear principle: strict validation with maximum user-friendliness. Each alias entered is immediately checked for empty values, duplicate entries, and syntactic correctness. Errors are visualised directly in the input field, providing the user with immediate feedback. This direct feedback builds trust in the input and prevents incomplete or erroneous data from reaching the persistence layer.</p><p>A key feature of this component is its interaction with the application&rsquo;s event infrastructure. As soon as a new alias is saved or an existing alias is changed, the editor triggers an event that propagates across the entire UI. This means that other views – such as the overview list – are also automatically updated without requiring a reload. This event control ensures a reactive user interface that always reflects the current state.</p><p>From a technical perspective, multi-alias management illustrates how consistently implemented user orientation and clean architecture reinforce one another. The system remains modular, with components clearly separated, yet the user perceives a consistent, organic interface. What was previously a sequential process – create, check, correct – becomes an interactive dialogue with the system, which reacts to the user&rsquo;s actions in real time.</p><p>Thus, MultiAliasEditorStrict is not only the heart of the new alias logic but also a harbinger of future developments. Its architecture lays the foundation for further interactions such as mass edits, alias groupings or time-controlled alias activations. In this sense, the component exemplifies the project&rsquo;s evolution from a functional tool to a mature, user-centred application.</p><h3 id="source-code-and-explanations">Source code and explanations</h3><h3 id="multialiaseditorstrict--core-of-multialias-management-excerpt">MultiAliasEditorStrict – Core of Multialias Management (excerpt)</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.urlshortener.ui.vaadin.components;</span></span><span class="line"><span class="cl">SNIPP</span></span><span class="line"><span class="cl">public class MultiAliasEditorStrict extends VerticalLayout {</span></span><span class="line"><span class="cl"> private static final String RX = "^[A-Za-z0-9_-]{3,64}$";</span></span><span class="line"><span class="cl"> private final Grid<span class="nt">&lt;Row&gt;</span> grid = new Grid<span class="err">&lt;</span>&gt;(Row.class, false);</span></span><span class="line"><span class="cl"> private final TextArea bulk = new TextArea("Aliases (comma/space/newline)");</span></span><span class="line"><span class="cl"> private final Button insertBtn = new Button("Take over");</span></span><span class="line"><span class="cl"> private final Button validateBtn = new Button("Validate all");</span></span><span class="line"><span class="cl"> private final String baseUrl;</span></span><span class="line"><span class="cl"> private final Function<span class="nt">&lt;String</span><span class="err">,</span><span class="err">Boolean</span><span class="nt">&gt;</span> isAliasFree; Server check (true = free)</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public MultiAliasEditorStrict(String baseUrl, Function<span class="nt">&lt;String</span><span class="err">,</span><span class="err">Boolean</span><span class="nt">&gt;</span> isAliasFree) {</span></span><span class="line"><span class="cl"> this.baseUrl = baseUrl;</span></span><span class="line"><span class="cl"> this.isAliasFree = isAliasFree;</span></span><span class="line"><span class="cl"> build();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> ==== Public API for the parent view ====</span></span><span class="line"><span class="cl"> public void validateAll() {</span></span><span class="line"><span class="cl"> var items = new ArrayList<span class="err">&lt;</span>&gt;(grid.getListDataView().getItems().toList());</span></span><span class="line"><span class="cl"> items.forEach(this::validateRow);</span></span><span class="line"><span class="cl"> grid.getDataProvider().refreshAll();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public List<span class="nt">&lt;String&gt;</span> getValidAliases() {</span></span><span class="line"><span class="cl"> return grid</span></span><span class="line"><span class="cl"> .getListDataView()</span></span><span class="line"><span class="cl"> .getItems()</span></span><span class="line"><span class="cl"> .filter(r -&gt; r.getStatus() == Status.VALID)</span></span><span class="line"><span class="cl"> .map(Row::getAlias)</span></span><span class="line"><span class="cl"> .collect(Collectors.toList());</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public void markSaved(String alias) { setStatus(alias, Status.SAVED, "saved"); }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public void markError(String alias, String message) {</span></span><span class="line"><span class="cl"> setStatus(alias, Status.ERROR, (message == null ? "error" : message));</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public long countOpen() {</span></span><span class="line"><span class="cl"> return grid.getListDataView().getItems()</span></span><span class="line"><span class="cl"> .filter(r -&gt; r.getStatus() != Status.SAVED).count();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public void clearAllRows() { grid.setItems(new ArrayList<span class="err">&lt;</span>&gt;()); }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private void setStatus(String alias, Status s, String msg) {</span></span><span class="line"><span class="cl"> grid.getListDataView().getItems().forEach(r -&gt; {</span></span><span class="line"><span class="cl"> if (Objects.equals(r.getAlias(), alias)) {</span></span><span class="line"><span class="cl"> r.setStatus(s);</span></span><span class="line"><span class="cl"> r.setMsg(msg);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"> grid.getDataProvider().refreshAll();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p><strong>Explanation.</strong> The component encapsulates all alias management within a standalone UI component. The constructor injects the baseUrl and a server-side availability-check function, keeping the editor testable and decoupling it from specific services. The public API provides the calling view with precise control points: validate all entries, extract valid aliases, set the status of individual rows to &ldquo;SAVED&rdquo; after successful saving, mark error states and count open works. The setStatus method updates the row status and triggers a UI refresh of the Grid DataProvider so that users can see the feedback immediately.</p><h3 id="uievent-for-consistent-refreshing">UIEvent for consistent refreshing</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import com.vaadin.flow.component.Component;</span></span><span class="line"><span class="cl">import com.vaadin.flow.component.ComponentEvent;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public class MappingCreatedOrChanged extends ComponentEvent<span class="nt">&lt;Component&gt;</span> {</span></span><span class="line"><span class="cl"> public MappingCreatedOrChanged(Component source) { super(source, false); }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p><strong>Explanation.</strong> This minimalist event is the link between the detail dialogue, create view and overview. After successfully saving new aliases, the UI fires this event; the OverviewView registers a listener and reloads its DataProvider. For the user, this means that changes are immediately visible without manual reloading.</p><h3 id="server-side-redirect--consistent-methodology-control-excerpt">Server-side redirect – consistent methodology control (excerpt)</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handle</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">exchange</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="w"/><span class="n">RequestMethodUtils</span><span class="p">.</span><span class="na">requireGet</span><span class="p">(</span><span class="n">exchange</span><span class="p">))</span><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// ... further code</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Explanation.</strong> Recurring checks and error handling are outsourced to utility methods. This reduces dispersion, prevents copy-and-paste divergences and increases the robustness of the routes. This principle is also reflected in the UI: validation, preparation, and persistence are clearly separated and orchestrated through well-defined interfaces.</p><p><strong>Note on the presentation.</strong> In this chapter, only the excerpts that are decisive for user guidance are shown. The complete files, as well as the context of the private helper methods (build(), validateRow(&hellip;), bulk input parser, row POJO, and status enum), are located in the<a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-06">feature/advent-2025-day-06</a> branch. The public API methods access them directly and map the validation-driven interaction flow.</p><h2 id="intelligent-refresh-of-the-overview">Intelligent refresh of the overview</h2><p>The integration of multi-alias management would be incomplete if changes to existing short links were not immediately visible in the overview. This is precisely where intelligent refresh comes in. The user should no longer be required to refresh the view after manually adding or changing aliases. Instead, the application automatically reacts to relevant events and ensures that the displayed data reflects the current state at all times.</p><p>Technically, this behaviour is implemented via Vaadin&rsquo;s global EventBus. As soon as the MappingCreatedOrChanged event is triggered in the DetailsDialog, the OverviewView registers the signal and internally triggers the update process. The user thus experiences a seamless interaction between editing and overview. The logic responsible for this can be found directly in the constructor of the OverviewView:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="n">ComponentUtil</span><span class="p">.</span><span class="na">addListener</span><span class="p">(</span><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">MappingCreatedOrChanged</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Received MappingCreatedOrChanged -&gt; refreshing overview"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refreshPageInfo</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span></span></span></code></pre></div></div><p>This code shows how the browser dynamically responds to changes in other UI components. The crucial part is registering the event listener with ComponentUtil.addListener. Every time a MappingCreatedOrChanged event is raised—for example, by the DetailsDialog after saving new aliases—the OverviewView receives it. The refreshPageInfo() and refresh() methods ensure that the displayed data is reloaded. As a result, status indicators, counters, and table contents always remain up to date without requiring user intervention.</p><p>Essential to this implementation is its simplicity and independence. The OverviewView does not know the source of the change. It only reacts to the generic event and fetches the data fresh from the server. This loosely coupled design prevents circular dependencies and makes the system robust to extensions. Future components that also trigger alias changes will be able to use the same event without requiring an adjustment to the overview.</p><p>It is also noteworthy that the EventBus system in Vaadin enables an apparent decoupling between UI elements. The DetailsDialog and the OverviewView share neither direct references nor mutual knowledge of their respective states. They communicate exclusively about events. This architecture is not only elegant, but also scalable. It enables future expansion of the system with additional listeners, such as notifications, logging, or statistics updates.</p><p>From the user&rsquo;s perspective, this results in a reactive, immediate application experience. Changes to aliases or destination addresses are reflected in the overview without any noticeable delay. The user remains in the workflow and is always sure that what he sees reflects the current data state. This seemingly minor adjustment transforms the URL shortener&rsquo;s operation into a modern, responsive interaction model that combines transparency and efficiency.</p><h2 id="improvements-in-the-create-view">Improvements in the Create View</h2><p>As part of this development step, the Create View also underwent a comprehensive overhaul. The aim was to adapt the process for creating new short URLs more closely to the updated user logic and, at the same time, to standardise workflows. While the detail dialogue is primarily responsible for maintaining existing mappings, the Create View serves as an entry point for new entries, now with the same convenience functions as the edit view.</p><figure><img src="/images/2025/12/image-28-1024x321.png" alt="Screenshot of a URL shortener interface displaying the ‘Create’ view. On the left, a form for creating short links includes fields for the target URL, expiration date, and time. On the right, a list of aliases with their corresponding preview and status, indicating if they are valid or not. There are buttons for saving, resetting, and validating aliases." loading="lazy" decoding="async"/><p>The new version of the Create View offers a more transparent structure and uses a split layout to arrange form elements and preview information side by side. This allows the user to immediately see which aliases they are creating and how they relate to the destination URL. In addition, MultiAliasEditorStrict has been fully integrated, allowing multiple aliases to be captured directly, even when creating a new short URL.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.urlshortener.ui.vaadin.views;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">SNIPP all imports</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">import static com.svenruppert.urlshortener.core.DefaultValues.SHORTCODE_BASE_URL;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">@Route(value = CreateView.PATH, layout = MainLayout.class)</span></span><span class="line"><span class="cl">public class CreateView</span></span><span class="line"><span class="cl"> extends VerticalLayout</span></span><span class="line"><span class="cl"> implements HasLogger {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public static final String PATH = "create";</span></span><span class="line"><span class="cl"> private static final ZoneId ZONE = ZoneId.systemDefault();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private final URLShortenerClient urlShortenerClient = UrlShortenerClientFactory.newInstance();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private final TextField urlField = new TextField("Target URL");</span></span><span class="line"><span class="cl"> private final DatePicker expiresDate = new DatePicker("Expires (date)");</span></span><span class="line"><span class="cl"> private final TimePicker expiresTime = new TimePicker("Expires (time)");</span></span><span class="line"><span class="cl"> private final Checkbox noExpiry = new Checkbox("No expiry");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public CreateView() {</span></span><span class="line"><span class="cl"> setSpacing(true);</span></span><span class="line"><span class="cl"> setPadding(true);</span></span><span class="line"><span class="cl"> setSizeFull();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> urlField.setWidthFull();</span></span><span class="line"><span class="cl"> Button saveAllButton = new Button("Save");</span></span><span class="line"><span class="cl"> saveAllButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);</span></span><span class="line"><span class="cl"> Button resetButton = new Button("Reset");</span></span><span class="line"><span class="cl"> resetButton.addThemeVariants(ButtonVariant.LUMO_TERTIARY);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> configureExpiryFields();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> FormLayout form = new FormLayout();</span></span><span class="line"><span class="cl"> form.add(urlField, noExpiry, expiresDate, expiresTime);</span></span><span class="line"><span class="cl"> form.setResponsiveSteps(</span></span><span class="line"><span class="cl"> new FormLayout.ResponsiveStep("0", 1),</span></span><span class="line"><span class="cl"> new FormLayout.ResponsiveStep("900px", 2)</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl"> form.setColspan(urlField, 2);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> HorizontalLayout actions = new HorizontalLayout(saveAllButton, resetButton);</span></span><span class="line"><span class="cl"> actions.setWidthFull();</span></span><span class="line"><span class="cl"> actions.setJustifyContentMode(JustifyContentMode.START);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Binder<span class="nt">&lt;ShortenRequest&gt;</span> binder = new Binder<span class="err">&lt;</span>&gt;(ShortenRequest.class);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> binder.forField(urlField)</span></span><span class="line"><span class="cl"> .asRequired("URL must not be empty")</span></span><span class="line"><span class="cl"> .withValidator((String url, ValueContext _) -&gt; {</span></span><span class="line"><span class="cl"> var res = UrlValidator.validate(url);</span></span><span class="line"><span class="cl"> return res.valid() ? ValidationResult.ok() : ValidationResult.error(res.message());</span></span><span class="line"><span class="cl"> })</span></span><span class="line"><span class="cl"> .bind(ShortenRequest::getUrl, ShortenRequest::setUrl);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var editor = new MultiAliasEditorStrict(</span></span><span class="line"><span class="cl"> SHORTCODE_BASE_URL,</span></span><span class="line"><span class="cl"> alias -&gt; {</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> return urlShortenerClient.resolveShortcode(alias) == null;</span></span><span class="line"><span class="cl"> } catch (IOException e) {</span></span><span class="line"><span class="cl"> throw new RuntimeException(e);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl"> editor.setSizeFull();</span></span><span class="line"><span class="cl"> editor.getStyle().set("padding", "var(--lumo-space-m)");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> saveAllButton.addClickListener(_ -&gt; {</span></span><span class="line"><span class="cl"> var validated = binder.validate();</span></span><span class="line"><span class="cl"> if (validated.hasErrors()) return;</span></span><span class="line"><span class="cl"> if (!validateExpiryInFuture()) return;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> if (urlField.getValue() == null || urlField.getValue().isBlank()) {</span></span><span class="line"><span class="cl"> Notification.show("Target URL is empty", 2500, Notification.Position.TOP_CENTER);</span></span><span class="line"><span class="cl"> return;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> editor.validateAll();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> List<span class="nt">&lt;String&gt;</span> validAliases = editor.getValidAliases();</span></span><span class="line"><span class="cl"> if (validAliases.isEmpty()) {</span></span><span class="line"><span class="cl"> Notification.show("No valid aliases to save", 2000, Notification.Position.TOP_CENTER);</span></span><span class="line"><span class="cl"> return;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Optional<span class="nt">&lt;Instant&gt;</span> expiresAt = computeExpiresAt();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> int ok = 0;</span></span><span class="line"><span class="cl"> for (String alias : validAliases) {</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> logger().info("try to save mapping {} / {} ", urlField.getValue(), alias);</span></span><span class="line"><span class="cl"> var customMapping = urlShortenerClient.createCustomMapping(alias, urlField.getValue(), expiresAt.orElse(null));</span></span><span class="line"><span class="cl"> logger().info("created customMapping is {}", customMapping);</span></span><span class="line"><span class="cl"> if (customMapping != null)</span></span><span class="line"><span class="cl"> logger().info("saved - {}", customMapping);</span></span><span class="line"><span class="cl"> else logger().info("save failed for target {} with alias {}", urlField.getValue(), alias);</span></span><span class="line"><span class="cl"> editor.markSaved(alias);</span></span><span class="line"><span class="cl"> ok++;</span></span><span class="line"><span class="cl"> } catch (Exception ex) {</span></span><span class="line"><span class="cl"> editor.markError(alias, String.valueOf(ex.getMessage()));</span></span><span class="line"><span class="cl"> logger().info("failed to save URL with alias {}", alias);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> Notification.show("Saved: " + ok + " | Open: " + editor.countOpen(), 3500, Notification.Position.TOP_CENTER);</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> resetButton.addClickListener(_ -&gt; {</span></span><span class="line"><span class="cl"> clearFormAll(binder);</span></span><span class="line"><span class="cl"> editor.clearAllRows();</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> — SplitLayout</span></span><span class="line"><span class="cl"> var leftCol = new VerticalLayout(new H2("Create new short links"), form, actions);</span></span><span class="line"><span class="cl"> leftCol.setPadding(false);</span></span><span class="line"><span class="cl"> leftCol.setSpacing(true);</span></span><span class="line"><span class="cl"> leftCol.setSizeFull();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var rightCol = new VerticalLayout(new H2("Aliases"), editor);</span></span><span class="line"><span class="cl"> rightCol.setPadding(false);</span></span><span class="line"><span class="cl"> rightCol.setSpacing(true);</span></span><span class="line"><span class="cl"> rightCol.setSizeFull();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> SplitLayout split = new SplitLayout(leftCol, rightCol);</span></span><span class="line"><span class="cl"> split.setSizeFull();</span></span><span class="line"><span class="cl"> split.setSplitterPosition(40);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> add(split);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//... SNIPP</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private void clearFormAll(Binder<span class="nt">&lt;ShortenRequest&gt;</span> binder) {</span></span><span class="line"><span class="cl"> urlField.clear();</span></span><span class="line"><span class="cl"> noExpiry.clear();</span></span><span class="line"><span class="cl"> expiresDate.clear();</span></span><span class="line"><span class="cl"> expiresTime.clear();</span></span><span class="line"><span class="cl"> binder.setBean(new ShortenRequest());</span></span><span class="line"><span class="cl"> urlField.setInvalid(false);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This implementation makes it clear that the Create-View is no longer just a simple input form, but now has the same range of functions as the detail dialogue. The user can capture multiple aliases, validate them directly, and save them with a single click. The integration of the MappingCreatedOrChanged event also ensures that the overview is automatically updated whenever a new mapping is created.</p><p>The split layout supports a clear visual separation between data entry and alias management. The user remains in the context of their input while also seeing how the selected aliases behave relative to the target URL. The reset function lets you reset inputs without reloading the page.</p><p>This revision made the Create View an integral part of the consistent user experience. It shares the validation and event logic with the detailed dialogue and thus serves as the foundation for all alias management. The user benefits from consistent behavior in both contexts, while the code remains clearer, more maintainable, and more extensible by reusing key components.</p><h2 id="consistent-validation-and-error-feedback">Consistent validation and error feedback</h2><p>The growing complexity of the URL shortener&rsquo;s user interface required validation logic that was both reliable and transparent. With the addition of multiple aliases, it became essential to provide precise feedback to the user when inputs are incomplete, duplicated, or syntactically incorrect. This feedback had to appear directly within the context of the respective component to avoid interrupting the interaction flow.</p><p>The validation system was therefore consistently integrated into Vaadin&rsquo;s existing binder mechanism. The central entry point for this is the Create View, where the target URL field undergoes a strict validation. The implementation uses a dedicated UrlValidator that checks syntactic validity and schema compliance. The following excerpt from the Create View shows the binding and validation of the URL field:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"> Binder<span class="nt">&lt;ShortenRequest&gt;</span> binder = new Binder<span class="err">&lt;</span>&gt;(ShortenRequest.class);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> binder.forField(urlField)</span></span><span class="line"><span class="cl"> .asRequired("URL must not be empty")</span></span><span class="line"><span class="cl"> .withValidator((String url, ValueContext _) -&gt; {</span></span><span class="line"><span class="cl"> var res = UrlValidator.validate(url);</span></span><span class="line"><span class="cl"> return res.valid() ? ValidationResult.ok() : ValidationResult.error(res.message());</span></span><span class="line"><span class="cl"> })</span></span><span class="line"><span class="cl"> .bind(ShortenRequest::getUrl, ShortenRequest::setUrl);</span></span></code></pre></div></div><p>This code demonstrates the interaction of mandatory field checking and semantic validation. The validator provides a precise error message for invalid inputs, which is displayed directly below the input field. The user can immediately see why an input was rejected. In this way, Vaadin&rsquo;s classic form validation is supplemented with project-specific logic.</p><p>A similar principle is also used in MultiAliasEditorStrict, which checks each alias entry line by line. In addition to checking syntactic correctness, we also check whether an alias has already been assigned. Visual feedback is provided directly in the alias table, allowing the user to distinguish between valid, saved, and incorrect entries immediately. An excerpt from the corresponding class illustrates this mechanic:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">validateAll</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">items</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">grid</span><span class="p">.</span><span class="na">getListDataView</span><span class="p">().</span><span class="na">getItems</span><span class="p">().</span><span class="na">toList</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">items</span><span class="p">.</span><span class="na">forEach</span><span class="p">(</span><span class="k">this</span><span class="p">::</span><span class="n">validateRow</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getDataProvider</span><span class="p">().</span><span class="na">refreshAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">validateRow</span><span class="p">(</span><span class="n">Row</span><span class="w"/><span class="n">r</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">a</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNullElse</span><span class="p">(</span><span class="n">r</span><span class="p">.</span><span class="na">getAlias</span><span class="p">(),</span><span class="w"/><span class="s">""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">a</span><span class="p">.</span><span class="na">matches</span><span class="p">(</span><span class="n">RX</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="p">.</span><span class="na">INVALID_FORMAT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setMsg</span><span class="p">(</span><span class="s">"3–64: A–Z a–z 0–9 - _"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">same</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getListDataView</span><span class="p">().</span><span class="na">getItems</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">x</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">x</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="n">r</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="w"/><span class="n">x</span><span class="p">.</span><span class="na">getAlias</span><span class="p">()))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">count</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">same</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="p">.</span><span class="na">CONFLICT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setMsg</span><span class="p">(</span><span class="s">"Duplicate in list"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isAliasFree</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">isAliasFree</span><span class="p">.</span><span class="na">apply</span><span class="p">(</span><span class="n">a</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="p">.</span><span class="na">CONFLICT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setMsg</span><span class="p">(</span><span class="s">"Alias taken"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="p">.</span><span class="na">ERROR</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setMsg</span><span class="p">(</span><span class="s">"Check failed"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setStatus</span><span class="p">(</span><span class="n">Status</span><span class="p">.</span><span class="na">VALID</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">r</span><span class="p">.</span><span class="na">setMsg</span><span class="p">(</span><span class="s">""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The validateAll() method iterates over all alias lines and calls an internal check for each. This validation is strict but transparent to the user: each alias line is given its own status indicator and text message. This means that error states are not collected as a list; they are displayed directly within the visual context. This form of inline validation complies with modern UI principles and minimises cognitive interruptions during data entry.</p><p>Another strength of this architecture is the standardisation of error feedback. Whether it&rsquo;s an invalid URL, an empty alias, or a duplicate name, every discrepancy is handled the same way and communicated through the exact mechanism. This reduces inconsistencies and creates reliable feedback behaviour that users can intuitively understand. At the same time, the code remains easily extensible: new check rules can be added without modifying the existing logic.</p><p>The interaction between binder validation and alias-related status display thus creates a coherent feedback system. The user immediately learns which inputs have been accepted, which need correction, and when a record has been successfully saved. As a result, validation is no longer perceived as a hurdle, but as an integral part of an accurate and trustworthy user experience.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Part-One-Green.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Part-One-Green.jpeg"/><enclosure url="https://svenruppert.com/images/2025/12/Part-One-Green.jpeg" type="image/jpeg" length="0"/></item><item><title>Advent Calendar - 2025 - From Grid to Detail: Understanding the User Experience in the Short-URL Manager</title><link>https://svenruppert.com/posts/advent-calendar-2025-from-grid-to-detail-understanding-the-user-experience-in-the-short-url-manager/</link><pubDate>Wed, 10 Dec 2025 10:32:12 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-from-grid-to-detail-understanding-the-user-experience-in-the-short-url-manager/</guid><description>The current UI from the user&rsquo;s point of view On the first call, the user lands in the overview. The site is built on a Vaadin grid, whose header contains a search bar, paging controls, and a small settings button with a gear icon. The most essential flow begins with the table displaying immediately understandable columns: the shortcode as a clearly typographically separated monospace value with copy action, the original URL as a clickable link, a creation time in local format, and an expiration badge that visually communicates semantic states such as &ldquo;Expired&rdquo;, &ldquo;Today&rdquo; or &ldquo;in n days&rdquo; via theme colours. The whole thing is designed for quick viewing and efficient one-handed operation: a click on a data record opens the detailed dialogue if required; a right-click or the context menu offers direct quick actions; and the gear button can be used to show or hide visible columns live.</description><content:encoded>&lt;![CDATA[<h2 id="the-current-ui-from-the-users-point-of-view">The current UI from the user&rsquo;s point of view</h2><p>On the first call, the user lands in the overview. The site is built on a Vaadin grid, whose header contains a search bar, paging controls, and a small settings button with a gear icon. The most essential flow begins with the table displaying immediately understandable columns: the shortcode as a clearly typographically separated monospace value with copy action, the original URL as a clickable link, a creation time in local format, and an expiration badge that visually communicates semantic states such as &ldquo;Expired&rdquo;, &ldquo;Today&rdquo; or &ldquo;in n days&rdquo; via theme colours. The whole thing is designed for quick viewing and efficient one-handed operation: a click on a data record opens the detailed dialogue if required; a right-click or the context menu offers direct quick actions; and the gear button can be used to show or hide visible columns live.</p><p><strong>The source code for this version can be found on GitHub at</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-05">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-05</a></p><p>Central to everyday life is the small &ldquo;Settings&rdquo; button on the right of the search bar. It opens the column dialogue and directly affects the grid. This gives the user a lightweight tool to customise the table to their needs without losing context. In the dialogue, the user sees a tidy list of checkboxes—each named after the column header or the internal key. Each click immediately shows or hides the corresponding column; the state is persisted, so that when you revisit it, the view appears as if you had left. The behaviour is deliberately kept minimalist: no &ldquo;Apply&rdquo; button, but immediate feedback, supplemented by an &ldquo;Apply bulk&rdquo; option for collective changes.</p><figure><img src="/images/2025/12/image-21-1024x526.png" alt="A user interface displaying a column selection dialog with checkboxes for columns including ‘Shortcode’, ‘Created’, ‘URL’, ‘Expires’, and ‘Actions’, along with buttons for ‘Close’ and ‘Apply bulk’." loading="lazy" decoding="async"/><p>The table is optimised for common workflows. The shortcode is formatted as a monospace span, and a copy button is right next to it that copies the full short URL to the clipboard. This eliminates the need to highlight text; a quick handover in chat, issue tracker, or email is done with one click. The original URL opens in a new tab, allowing the user to review the landing page without losing track. The creation and expiration times are kept compact; the expiration badge changes colour depending on the remaining term, signalling urgency.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="na">addComponentColumn</span><span class="p">(</span><span class="n">m</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">code</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"font-family"</span><span class="p">,</span><span class="w"/><span class="s">"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">copy</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Icon</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">COPY</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">copy</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_TERTIARY_INLINE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">copy</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="s">"Copy ShortUrl"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">copy</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">getPage</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">executeJs</span><span class="p">(</span><span class="s">"navigator.clipboard.writeText($0)"</span><span class="p">,</span><span class="w"/><span class="n">SHORTCODE_BASE_URL</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Shortcode copied"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">code</span><span class="p">,</span><span class="w"/><span class="n">copy</span><span class="p">,</span><span class="w"/><span class="n">open</span><span class="p">,</span><span class="w"/><span class="n">details</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">})</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Shortcode"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setKey</span><span class="p">(</span><span class="s">"shortcode"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setResizable</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setFlexGrow</span><span class="p">(</span><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="na">addColumn</span><span class="p">(</span><span class="n">m</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">DATE_TIME_FMT</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">createdAt</span><span class="p">()))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Created"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setKey</span><span class="p">(</span><span class="s">"created"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setResizable</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setSortable</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setFlexGrow</span><span class="p">(</span><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="na">addComponentColumn</span><span class="p">(</span><span class="n">m</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">pill</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">expiresAt</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">ts</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">days</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Duration</span><span class="p">.</span><span class="na">between</span><span class="p">(</span><span class="n">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">(),</span><span class="w"/><span class="n">ts</span><span class="p">).</span><span class="na">toDays</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">days</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Expired"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">days</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Today"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"in "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">days</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" days"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">})</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">orElse</span><span class="p">(</span><span class="s">"No expiry"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pill</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">getThemeList</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"badge pill small"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">pill</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">})</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Expires"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setKey</span><span class="p">(</span><span class="s">"expires"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span></span></span></code></pre></div></div><p>In addition to the dialogue, there is a second, faster entry point via the context menu. Right-clicking on a line opens actions such as &ldquo;Show details&rdquo;, &ldquo;Open URL&rdquo;, &ldquo;Copy shortcode&rdquo; and &ldquo;Delete&hellip;&rdquo;. This is especially useful if the user already knows what they want to do with the current record without leaving the main view. The flow remains fluid because every action docks directly to the grid.</p><figure><img src="/images/2025/12/image-20-1024x540.png" alt="Overview of user interface displaying a table with shortcode and URL columns, including options for showing details, opening URLs, copying shortcodes, and deleting entries." loading="lazy" decoding="async"/><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">GridContextMenu<span class="nt">&lt;ShortUrlMapping&gt;</span> menu = new GridContextMenu<span class="err">&lt;</span>&gt;(grid);</span></span><span class="line"><span class="cl">menu.addItem("Show details", e -&gt; e.getItem().ifPresent(this::openDetailsDialog));</span></span><span class="line"><span class="cl">menu.addItem("Open URL", e -&gt; e.getItem().ifPresent(m -&gt;</span></span><span class="line"><span class="cl"> UI.getCurrent().getPage().open(m.originalUrl(), "_blank")));</span></span><span class="line"><span class="cl">menu.addItem("Copy shortcode", e -&gt; e.getItem().ifPresent(m -&gt;</span></span><span class="line"><span class="cl"> UI.getCurrent().getPage().executeJs("navigator.clipboard.writeText($0)", m.shortCode())));</span></span><span class="line"><span class="cl">menu.addItem("Delete...", e -&gt; e.getItem().ifPresent(m -&gt; confirmDelete(m.shortCode())));</span></span></code></pre></div></div><p>If the user wants to know more or make changes, the application opens a standalone detail dialogue for the selected entry. From an interaction perspective, this is where deeper information and operations converge. The appeal remains deliberately unspectacular and fast:</p><figure><img src="/images/2025/12/image-22-1024x542.png" alt="A modal dialog displaying details for a shortened URL, including fields for shortcode, original URL, creation date, and expiration status. It features buttons for opening the URL, copying the shortcode and original URL, editing, and deleting the record." loading="lazy" decoding="async"/><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">openDetailsDialog</span><span class="p">(</span><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">item</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">dlg</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DetailsDialog</span><span class="p">(</span><span class="n">urlShortenerClient</span><span class="p">,</span><span class="w"/><span class="n">item</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">addDeleteListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">confirmDelete</span><span class="p">(</span><span class="n">ev</span><span class="p">.</span><span class="na">shortCode</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">addOpenListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Open URL {}"</span><span class="p">,</span><span class="w"/><span class="n">ev</span><span class="p">.</span><span class="na">originalUrl</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">addCopyShortListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Copied shortcode {}"</span><span class="p">,</span><span class="w"/><span class="n">ev</span><span class="p">.</span><span class="na">shortCode</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">addCopyUrlListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Copied URL {}"</span><span class="p">,</span><span class="w"/><span class="n">ev</span><span class="p">.</span><span class="na">url</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">addSavedListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">refresh</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">open</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>As a result, the overview feels like a familiar workboard for the user, which remembers which columns really count, can be copied and opened quickly, and with context menus and a focused detail dialogue offers exactly the depth of interaction that is needed in everyday short URL life — no more, but also no less.</p><h2 id="detail-dialogue-on-the-data-record">Detail dialogue on the data record</h2><p>The detail dialogue is the central interaction element when users want to take a closer look at or edit a single entry in the system. It provides all relevant information about a shortened URL and enables basic operations, such as opening, copying, deleting, or saving the record. All features are designed to be available within the application without context switching.</p><p>The implementation of the dialogue in the project is in the DetailsDialog class. This class encompasses all the logic for displaying and manipulating a<code>ShortUrlMapping</code> object. The dialogue is built entirely with Vaadin components:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.urlshortener.ui.vaadin.views.overview;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//SNIPP</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public class DetailsDialog extends Dialog implements HasLogger {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private final URLShortenerClient urlShortenerClient;</span></span><span class="line"><span class="cl"> private final ShortUrlMapping mapping;</span></span><span class="line"><span class="cl"> private final DateTimeFormatter DATE_TIME_FMT=</span></span><span class="line"><span class="cl"> DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")</span></span><span class="line"><span class="cl"> .withZone(ZoneId.systemDefault());</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public DetailsDialog(URLShortenerClient urlShortenerClient, ShortUrlMapping mapping) {</span></span><span class="line"><span class="cl"> this.urlShortenerClient = urlShortenerClient;</span></span><span class="line"><span class="cl"> this.mapping = mapping;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> setHeaderTitle("Details for " + mapping.shortCode());</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var form = new FormLayout();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var urlField = new TextField("Original URL");</span></span><span class="line"><span class="cl"> urlField.setValue(Optional.ofNullable(mapping.originalUrl()).orElse(""));</span></span><span class="line"><span class="cl"> urlField.setWidthFull();</span></span><span class="line"><span class="cl"> urlField.setReadOnly(true);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var createdAt = new Span(DATE_TIME_FMT.format(mapping.createdAt()));</span></span><span class="line"><span class="cl"> var expiresAt = mapping.expiresAt()</span></span><span class="line"><span class="cl"> .map(ts -&gt; {</span></span><span class="line"><span class="cl"> var days = Duration.between(Instant.now(), ts).toDays();</span></span><span class="line"><span class="cl"> if (days<span class="nt">&lt; 0</span><span class="err">)</span><span class="err">return</span><span class="err">"Expired";</span></span></span><span class="line"><span class="cl"><span class="err">if</span><span class="err">(</span><span class="na">days =</span><span class="s">=</span><span class="err">0)</span><span class="err">return</span><span class="err">"Today";</span></span></span><span class="line"><span class="cl"><span class="err">return</span><span class="err">"in</span><span class="err">"</span><span class="err">+</span><span class="err">days</span><span class="err">+</span><span class="err">"</span><span class="err">days";</span></span></span><span class="line"><span class="cl"><span class="err">})</span></span></span><span class="line"><span class="cl"><span class="err">.orElse("No</span><span class="err">expiry");</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="err">var</span><span class="na">expiresField =</span><span class="s">new</span><span class="err">Span(expiresAt);</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="err">form.addFormItem(urlField,</span><span class="err">"Original</span><span class="err">URL");</span></span></span><span class="line"><span class="cl"><span class="err">form.addFormItem(createdAt,</span><span class="err">"Created");</span></span></span><span class="line"><span class="cl"><span class="err">form.addFormItem(expiresField,</span><span class="err">"Expires");</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="err">var</span><span class="na">openBtn =</span><span class="s">new</span><span class="err">Button(new</span><span class="err">Icon(VaadinIcon.EXTERNAL_LINK));</span></span></span><span class="line"><span class="cl"><span class="err">openBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);</span></span></span><span class="line"><span class="cl"><span class="err">openBtn.getElement().setProperty("title",</span><span class="err">"Open</span><span class="err">original</span><span class="err">URL");</span></span></span><span class="line"><span class="cl"><span class="err">openBtn.addClickListener(_</span><span class="err">-</span><span class="nt">&gt;</span> {</span></span><span class="line"><span class="cl"> UI.getCurrent().getPage().open(mapping.originalUrl(), "_blank");</span></span><span class="line"><span class="cl"> fireEvent(new OpenEvent(this, mapping.originalUrl()));</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var copyShortBtn = new Button(new Icon(VaadinIcon.COPY));</span></span><span class="line"><span class="cl"> copyShortBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);</span></span><span class="line"><span class="cl"> copyShortBtn.getElement().setProperty("title", "Copy Short URL");</span></span><span class="line"><span class="cl"> copyShortBtn.addClickListener(_ -&gt; {</span></span><span class="line"><span class="cl"> UI.getCurrent().getPage()</span></span><span class="line"><span class="cl"> .executeJs("navigator.clipboard.writeText($0)", SHORTCODE_BASE_URL + mapping.shortCode());</span></span><span class="line"><span class="cl"> Notification.show("Short URL copied");</span></span><span class="line"><span class="cl"> fireEvent(new CopyShortEvent(this, mapping.shortCode()));</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var copyUrlBtn = new Button(new Icon(VaadinIcon.LINK));</span></span><span class="line"><span class="cl"> copyUrlBtn.addThemeVariants(ButtonVariant.LUMO_TERTIARY);</span></span><span class="line"><span class="cl"> copyUrlBtn.getElement().setProperty("title", "Copy Original URL");</span></span><span class="line"><span class="cl"> copyUrlBtn.addClickListener(_ -&gt; {</span></span><span class="line"><span class="cl"> UI.getCurrent().getPage()</span></span><span class="line"><span class="cl"> .executeJs("navigator.clipboard.writeText($0)", mapping.originalUrl());</span></span><span class="line"><span class="cl"> Notification.show("Original URL copied");</span></span><span class="line"><span class="cl"> fireEvent(new CopyUrlEvent(this, mapping.originalUrl()));</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var deleteBtn = new Button(new Icon(VaadinIcon.TRASH));</span></span><span class="line"><span class="cl"> deleteBtn.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);</span></span><span class="line"><span class="cl"> deleteBtn.getElement().setProperty("title", "Delete Short URL");</span></span><span class="line"><span class="cl"> deleteBtn.addClickListener(_ -&gt; {</span></span><span class="line"><span class="cl"> fireEvent(new DeleteEvent(this, mapping.shortCode()));</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var saveBtn = new Button("Save", _ -&gt; {</span></span><span class="line"><span class="cl"> urlShortenerClient.update(mapping);</span></span><span class="line"><span class="cl"> fireEvent(new SavedEvent(this));</span></span><span class="line"><span class="cl"> close();</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var buttons = new HorizontalLayout(openBtn, copyShortBtn, copyUrlBtn, deleteBtn, saveBtn);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> add(form, buttons);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public Registration addOpenListener(ComponentEventListener<span class="nt">&lt;OpenEvent&gt;</span> listener) {</span></span><span class="line"><span class="cl"> return addListener(OpenEvent.class, listener);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public Registration addCopyShortListener(ComponentEventListener<span class="nt">&lt;CopyShortEvent&gt;</span> listener) {</span></span><span class="line"><span class="cl"> return addListener(CopyShortEvent.class, listener);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public Registration addCopyUrlListener(ComponentEventListener<span class="nt">&lt;CopyUrlEvent&gt;</span> listener) {</span></span><span class="line"><span class="cl"> return addListener(CopyUrlEvent.class, listener);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public Registration addDeleteListener(ComponentEventListener<span class="nt">&lt;DeleteEvent&gt;</span> listener) {</span></span><span class="line"><span class="cl"> return addListener(DeleteEvent.class, listener);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public Registration addSavedListener(ComponentEventListener<span class="nt">&lt;SavedEvent&gt;</span> listener) {</span></span><span class="line"><span class="cl"> return addListener(SavedEvent.class, listener);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public static class OpenEvent extends ComponentEvent<span class="nt">&lt;DetailsDialog&gt;</span> {</span></span><span class="line"><span class="cl"> private final String originalUrl;</span></span><span class="line"><span class="cl"> public OpenEvent(DetailsDialog src, String originalUrl) {</span></span><span class="line"><span class="cl"> super(src, false);</span></span><span class="line"><span class="cl"> this.originalUrl = originalUrl;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public String originalUrl() { return originalUrl; }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public static class CopyShortEvent extends ComponentEvent<span class="nt">&lt;DetailsDialog&gt;</span> {</span></span><span class="line"><span class="cl"> private final String shortCode;</span></span><span class="line"><span class="cl"> public CopyShortEvent(DetailsDialog src, String shortCode) {</span></span><span class="line"><span class="cl"> super(src, false);</span></span><span class="line"><span class="cl"> this.shortCode = shortCode;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public String shortCode() { return shortCode; }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public static class CopyUrlEvent extends ComponentEvent<span class="nt">&lt;DetailsDialog&gt;</span> {</span></span><span class="line"><span class="cl"> private final String url;</span></span><span class="line"><span class="cl"> public CopyUrlEvent(DetailsDialog src, String url) {</span></span><span class="line"><span class="cl"> super(src, false);</span></span><span class="line"><span class="cl"> this.url = url;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public String url() { return url; }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public static class DeleteEvent extends ComponentEvent<span class="nt">&lt;DetailsDialog&gt;</span> {</span></span><span class="line"><span class="cl"> private final String shortCode;</span></span><span class="line"><span class="cl"> public DeleteEvent(DetailsDialog src, String shortCode) {</span></span><span class="line"><span class="cl"> super(src, false);</span></span><span class="line"><span class="cl"> this.shortCode = shortCode;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public String shortCode() { return shortCode; }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public static class SavedEvent extends ComponentEvent<span class="nt">&lt;DetailsDialog&gt;</span> {</span></span><span class="line"><span class="cl"> public SavedEvent(DetailsDialog src) { super(src, false); }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The architecture follows a clear pattern: the dialogue displays data but does not trigger a direct UI update. Instead, it fires specific events (<code>ComponentEvents</code>) that are caught by the calling View (<code>OverviewView</code>). This means that the dialogue remains independent of its environment – a concept that can be implemented particularly elegantly in Vaadin.</p><p>The meaning of the individual buttons is immediately understandable: the open button opens the browser with the original URL, the two copy buttons copy the respective address to the clipboard, and the delete button sends a delete event that is further processed in the overview. It is particularly noteworthy that the<code>save</code>button internally<code>calls urlShortenerClient.update(),</code> thereby synchronising changes directly through the existing client-server infrastructure.</p><p>This makes the detail dialogue a self-contained UI element that encapsulates both presentation and interaction without dragging business logic into the interface. This pattern promotes reusability, testability, and a clear separation of responsibilities.</p><h2 id="context-menu-of-grid-rows">Context menu of grid rows</h2><p>In the project, the context menu is anchored to the<code>OverviewView</code> and bound to the<code>ShortUrlMapping</code> objects‘ grid. The source code shows how this menu is structured and what actions are offered in it:``</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">GridContextMenu<span class="nt">&lt;ShortUrlMapping&gt;</span> menu = new GridContextMenu<span class="err">&lt;</span>&gt;(grid);</span></span><span class="line"><span class="cl">menu.addItem("Show details", e -&gt; e.getItem().ifPresent(this::openDetailsDialog));</span></span><span class="line"><span class="cl">menu.addItem("Open URL", e -&gt; e.getItem().ifPresent(m -&gt;</span></span><span class="line"><span class="cl"> UI.getCurrent().getPage().open(m.originalUrl(), "_blank")));</span></span><span class="line"><span class="cl">menu.addItem("Copy shortcode", e -&gt; e.getItem().ifPresent(m -&gt;</span></span><span class="line"><span class="cl"> UI.getCurrent().getPage().executeJs("navigator.clipboard.writeText($0)", m.shortCode())));</span></span><span class="line"><span class="cl">menu.addItem("Delete...", e -&gt; e.getItem().ifPresent(m -&gt; confirmDelete(m.shortCode())));</span></span></code></pre></div></div><p>The logic is simple but effective: each menu item performs a clearly defined action. These actions draw directly on the existing mechanisms of the<code>OverviewView</code> – such as the<code>openDetailsDialog()</code> for detail views or<code>confirmDelete()</code> for removing a data record.</p><p>Each menu action uses the method<code>e.getItem().ifPresent(...)</code>to ensure that a context object (that is, a selected entry in the grid) exists. This pattern protects the application from null references and makes the menu robust against unforeseen UI states.</p><p>The integration into the<code>OverviewView</code> is straightforward and follows Vaadin&rsquo;s component-oriented approach. By binding to the grid, the menu is automatically linked to the respective rows. Users can thus interact with the data in a context-sensitive manner – an ergonomic advantage over classic toolbar or button solutions.</p><p>The connection to the detail dialogue is seamless: If the user selects &ldquo;Show details&rdquo; in the context menu, the DetailsDialog for the corresponding<code>ShortUrlMapping</code> opens immediately. The interaction between the grid, context menu, and dialogue remains completely encapsulated within the view, keeping the code easy to maintain and understand.</p><h2 id="event-integration-between-overview-and-detaildialog">Event integration between Overview and DetailDialog</h2><p>The interaction between the<code>OverviewView</code> and the<code>DetailsDialog</code> is the functional heart of the editing workflow. It combines a tabular overview with a detailed view of individual datasets and ensures that changes in the dialogue are immediately reflected in the grid. The concept follows Vaadin&rsquo;s component-oriented event mechanism, in which UI components can trigger their own events, which are processed by other elements – in this case, the<code>OverviewView</code> .</p><p>In the<code>OverviewView</code>, the dialogue opens when the user selects &ldquo;Show details&rdquo; from the context menu or double-clicks an entry. The<code>openDetailsDialog()</code> method takes over this task and, at the same time, binds all relevant listeners to the dialogue:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">openDetailsDialog</span><span class="p">(</span><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">item</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">dlg</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DetailsDialog</span><span class="p">(</span><span class="n">urlShortenerClient</span><span class="p">,</span><span class="w"/><span class="n">item</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">addDeleteListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">confirmDelete</span><span class="p">(</span><span class="n">ev</span><span class="p">.</span><span class="na">shortCode</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">addOpenListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Open URL {}"</span><span class="p">,</span><span class="w"/><span class="n">ev</span><span class="p">.</span><span class="na">originalUrl</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">addCopyShortListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Copied shortcode {}"</span><span class="p">,</span><span class="w"/><span class="n">ev</span><span class="p">.</span><span class="na">shortCode</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">addCopyUrlListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Copied URL {}"</span><span class="p">,</span><span class="w"/><span class="n">ev</span><span class="p">.</span><span class="na">url</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">addSavedListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">refresh</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dlg</span><span class="p">.</span><span class="na">open</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Each listener processes a specific event triggered by the dialogue itself. The trick is that the<code>DetailsDialog</code> defines these events as their own, type-safe subclasses of<code>ComponentEvent</code>. This allows the<code>OverviewView to</code>respond to actions in a targeted manner without knowing the dialogue&rsquo;s implementation details. Examples include<code>DeleteEvent</code>,<code>CopyUrlEvent</code>,<code>CopyShortEvent</code>,<code>OpenEvent,</code> and<code>SavedEvent</code>.</p><p>The crucial point is the<code>refresh()</code> at the end of the listener chain. This ensures that after a change to the dialogue, the grid data is reloaded. This keeps the display consistent and immediately reflects the current persistence state.</p><p>The dialogue itself triggers these events as soon as user actions occur. In the event of a deletion action, this is done via:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">deleteBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fireEvent</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">DeleteEvent</span><span class="p">(</span><span class="k">this</span><span class="p">,</span><span class="w"/><span class="n">mapping</span><span class="p">.</span><span class="na">shortCode</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Or when copying the short URL:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">copyShortBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">getPage</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">executeJs</span><span class="p">(</span><span class="s">"navigator.clipboard.writeText($0)"</span><span class="p">,</span><span class="w"/><span class="n">SHORTCODE_BASE_URL</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">mapping</span><span class="p">.</span><span class="na">shortCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Short URL copied"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fireEvent</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">CopyShortEvent</span><span class="p">(</span><span class="k">this</span><span class="p">,</span><span class="w"/><span class="n">mapping</span><span class="p">.</span><span class="na">shortCode</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>By triggering its own events, the dialogue can operate decoupled from its environment. It doesn&rsquo;t know who calls it or what happens to the data afterwards – it just reports that an action occurred. This decoupling is an essential aspect of good UI architecture: it enables reuse and testable components without side effects.</p><p>The<code>OverviewView</code> then takes responsibility for updating the system in response to these events. Exquisite is the use of<code>ComponentEventListener</code>, which has type safety and clean demarcation of the event classes. Vaadin thus offers a clearly structured and idiomatic way to model complex UI flows.</p><p>Another example shows the connection between the deletion process and the server communication. When a<code>DeleteEvent</code> is fired, the<code>OverviewView calls</code>the internal method<code>confirmDelete()</code> to display a security prompt and, upon confirmation, delete the record server-side. Only then does the grid refresh again, so the removed entry disappears immediately.</p><p>This event-based integration ensures a consistent and reactive processing flow. Users experience changes in real time, without manual updates. For developers, the model offers a clear separation of responsibilities: the<code>DetailsDialog</code> encapsulates presentation and interaction, while<code>OverviewView</code> orchestrates and synchronises them. This creates a UI structure that can be flexibly expanded and easily supplemented with new actions.</p><h2 id="validation-and-error-feedback-in-the-dialogue">Validation and error feedback in the dialogue</h2><p>A central element of the edit dialogue is input validation. This is to ensure that only correct and complete data is transmitted to the server. The focus is on checking URLs, as they form the core of every<code>ShortUrlMapping</code>.</p><p>In the current project, validation is handled using the Vaadin<code>Binder</code>. The binder connects the UI fields to the properties of the underlying data model and provides built-in support for validations and error feedback.</p><p>A typical excerpt from the archive shows how this validation is implemented in practice:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">binder</span><span class="p">.</span><span class="na">forField</span><span class="p">(</span><span class="n">urlField</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">asRequired</span><span class="p">(</span><span class="s">"URL must not be empty"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">withValidator</span><span class="p">(</span><span class="n">url</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">validate</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UrlValidator</span><span class="p">.</span><span class="na">validate</span><span class="p">(</span><span class="n">url</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">validate</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">},</span><span class="w"/><span class="s">"Only HTTP(S) URLs allowed"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">bind</span><span class="p">(</span><span class="n">ShortUrlMapping</span><span class="p">::</span><span class="n">originalUrl</span><span class="p">,</span><span class="w"/><span class="kc">null</span><span class="p">);</span></span></span></code></pre></div></div><p>Several steps are combined here:</p><ol><li><strong>Required field check</strong> –<code>asRequired()</code> ensures that the field is not left empty. If no input is made, Vaadin automatically displays an error message.</li><li><strong>Content validation</strong> :<code>withValidator()</code> is also used to validate the URL format. This uses the<code>UrlValidator helper class</code>.</li></ol><p>The<code>UrlValidator</code> itself is a standalone utility class that syntactically checks whether a string is a valid HTTP or HTTPS URL. The implementation is:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.svenruppert.urlshortener.core.urlmapping</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.URI</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">UrlValidator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="nf">UrlValidator</span><span class="p">()</span><span class="w"/><span class="p">{}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">validate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">url</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">url</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">uri</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">URI</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="n">url</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">scheme</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">uri</span><span class="p">.</span><span class="na">getScheme</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">scheme</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="p">(</span><span class="n">scheme</span><span class="p">.</span><span class="na">equalsIgnoreCase</span><span class="p">(</span><span class="s">"http"</span><span class="p">)</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">scheme</span><span class="p">.</span><span class="na">equalsIgnoreCase</span><span class="p">(</span><span class="s">"https"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IllegalArgumentException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This implementation is deliberately kept simple and uses only standard Java means. It verifies that the URL has a valid schema and starts with HTTP or HTTPS. This excludes all other protocols – an important aspect to avoid potential security risks (e.g. file:// or javascript://).</p><p>The result of this validation is immediately displayed in the UI. If the user enters an invalid URL, an error message will appear below the input field that comes from the message set<code>withValidator()</code> (&ldquo;Only HTTP(S) URLs allowed&rdquo;). The Vaadin binder automatically controls this visual feedback.</p><p>The interaction among<code>Binder</code>,<code>TextField,</code> and<code>UrlValidator</code> ensures consistent, user-friendly, and secure input validation. In addition, the system can be expanded if necessary, for example, to include additional checks (e.g. URL accessibility, blacklist filters or regex-based structure tests) without changing the existing architecture.</p><p>This validation ensures that the<code>DetailsDialog</code> is not just a display element, but also a reliable filter that catches erroneous input before it enters the persistence layer. This reduces error scenarios, increases data quality, and significantly improves the user experience.</p><h2 id="ux-fine-tuning-and-conclusion">UX fine-tuning and conclusion</h2><p>The user experience (UX) of the interaction among the<code>OverviewView</code>, the<code>DetailsDialog</code>, and supporting UI components, such as the<code>ColumnVisibilityDialog,</code> is the result of numerous targeted design decisions. The goal was to create a reactive yet simple interface that avoids visual overload while still offering complete control over the data.</p><p>The application consistently follows Vaadin&rsquo;s guiding principles for a component-based UI. All interactions – from column selection to detail view – are encapsulated in clearly defined classes. The user benefits from an intuitive interaction logic: every action is available where it makes semantic sense, and every change is immediately visible.</p><p>An example of this UX approach is direct feedback on user actions. When a URL is copied or opened, a notification appears immediately:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Shortcode copied"</span><span class="p">);</span></span></span></code></pre></div></div><p>This micro-feedback improves the perception of the system response and confirms to the user that their action was successful. The same principle applies to several components, for example, when copying the original URL or saving it in the<code>DetailsDialog</code>.</p><p>Another part of the fine-tuning is the deliberate use of Vaadin theme variants to differentiate controls visually. For example, the delete button in the detail dialogue<code> clearly signals its critical function</code> through the combination of LUMO_ERROR<code>and</code>LUMO_TERTIARY:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">deleteBtn</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_ERROR</span><span class="p">,</span><span class="w"/><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_TERTIARY</span><span class="p">);</span></span></span></code></pre></div></div><p>This colouring adheres to the established Lumo design system and ensures that dangerous actions are immediately recognisable, without requiring additional explanatory text.</p><p>The modalization of the detail dialogue also contributes to user guidance:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">setModal</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">setDraggable</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">setResizable</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span></span></span></code></pre></div></div><p>This keeps the user&rsquo;s focus clearly on the task at hand, while dragging and resizing offer flexibility. Especially in data-rich grids, this allows the dialogue to adapt to the situation without leaving the application.</p><p>Another UX aspect is maintaining the work context. After each action – such as saving or deleting – the grid is reloaded by the<code>refresh()</code> method without losing filter or paging information. This was deliberately kept this way in the implementation:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">req</span><span class="p">=</span><span class="n">new</span><span class="n">UrlMappingListRequest</span><span class="p">(</span><span class="n">searchField</span><span class="p">.</span><span class="n">getValue</span><span class="p">(),</span><span class="n">pagination</span><span class="p">.</span><span class="n">getCurrentPage</span><span class="p">(),</span><span class="n">pagination</span><span class="p">.</span><span class="n">getPageSize</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">mappings</span><span class="p">=</span><span class="n">urlShortenerClient</span><span class="p">.</span><span class="n">list</span><span class="p">(</span><span class="n">req</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="n">setItems</span><span class="p">(</span><span class="n">mappings</span><span class="p">);</span></span></span></code></pre></div></div><p>This means the user remains in the same position within the data view and maintains their orientation and workflow rhythm.</p><p>The OverviewView&rsquo;s structure demonstrates how a consistent user experience can be achieved through small, targeted design decisions. Instead of complex UI frameworks, the application uses Vaadin&rsquo;s native component toolkit and combines it with a clear event architecture and lightweight HTTP communication.</p><h3 id="result">Result</h3><p>The implementation of the editing workflow via the DetailsDialog shows how a modern, reactive application flow can be created with pure Vaadin Flow and Core Java– without external frameworks, reflection, or client-side JavaScript logic. The key lies in clean component decoupling, precise event control, and immediate user feedback.</p><p>The user receives a UI that adapts to their workflows: fast, comprehensible and error-resistant. At the same time, developers can create a maintainable architecture with clear responsibilities: the view orchestrates, the dialogue interacts, and the client synchronises. This interplay of simplicity and clarity is the actual UX fine-tuning of this implementation – and at the same time, the basis for the further expansion stages of the project.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/ChatGPT-Image-10.-Dez.-2025-10_31_02.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/ChatGPT-Image-10.-Dez.-2025-10_31_02.png"/><enclosure url="https://svenruppert.com/images/2025/12/ChatGPT-Image-10.-Dez.-2025-10_31_02.png" type="image/jpeg" length="0"/></item><item><title>Advent Calendar - 2025 - ColumnVisibilityDialog - Part 2</title><link>https://svenruppert.com/posts/advent-calendar-2025-columnvisibilitydialog-part-2/</link><pubDate>Tue, 09 Dec 2025 09:55:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-columnvisibilitydialog-part-2/</guid><description>Server-Side Extension: PreferencesHandler and REST Interfaces The server-side extension for dynamic column visibility follows the same design logic as the UI: simplicity, clear accountability, and a precise data flow. While the OverviewView and the ColumnVisibilityDialog form the interface for the interaction, several specialized REST handlers handle the processing and persistence of the user settings. Their job is to process incoming JSON requests, validate them, translate them into domain operations, and return or store the current state.</description><content:encoded>&lt;![CDATA[<h2 id="server-side-extension-preferenceshandler-and-rest-interfaces">Server-Side Extension: PreferencesHandler and REST Interfaces</h2><p>The server-side extension for dynamic column visibility follows the same design logic as the UI: simplicity, clear accountability, and a precise data flow. While the OverviewView and the ColumnVisibilityDialog form the interface for the interaction, several specialized REST handlers handle the processing and persistence of the user settings. Their job is to process incoming JSON requests, validate them, translate them into domain operations, and return or store the current state.</p><p><strong>The source code for this version can be found on GitHub at</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-04">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-04</a></p><p>Here&rsquo;s the screenshot of the version we&rsquo;re implementing now.</p><p><figure><img src="/images/2025/12/image-18.png" alt="Screenshot of the URL Shortener overview page displaying search filters, a table with shortcodes, original URLs, created and expiry dates, and action buttons." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-19.png" alt="Screenshot of the URL Shortener Overview interface displaying a table with columns for Shortcode, URL, Created, Expires, and Actions. A modal for column visibility settings is open, showing checkboxes for selecting which columns to display." loading="lazy" decoding="async"/></p><p>The central link is the<code>PreferencesStore</code> interface, which defines both read and write operations for column preferences. It is divided into two functional aspects – loading and updating:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">PreferencesStore</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">PreferencesLookup</span><span class="p">,</span><span class="w"/><span class="n">PreferencesUpdater</span><span class="p">,</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">PreferencesLookup</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="o">&gt;</span><span class="w"/><span class="nf">load</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">PreferencesUpdater</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">saveColumnVisibilities</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">,</span><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="o">&gt;</span><span class="w"/><span class="n">visibility</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">delete</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">columnKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">delete</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">delete</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This structure forms the foundation for the subsequent merchant classes. Each REST endpoint includes a clearly defined operation—loading, editing, or deleting column preferences. This creates a granular API that is idempotent on the one hand and enables precise error handling on the other.</p><p>The first port of call is the<code>ColumnVisibilityHandler</code>, which accepts POST and DELETE requests. It is responsible for loading all of a user&rsquo;s column settings within a view. The handler first checks that the request is complete, and then returns a flat JSON map that maps column names and visibility states:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handle</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">switch</span><span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="na">getRequestMethod</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="s">"POST"</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">handleLoad</span><span class="p">(</span><span class="n">ex</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="s">"DELETE"</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">handleDeleteAll</span><span class="p">(</span><span class="n">ex</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="s">"OPTIONS"</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">allow</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span><span class="w"/><span class="s">"POST, DELETE, OPTIONS"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">default</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">methodNotAllowed</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span><span class="w"/><span class="s">"POST, DELETE, OPTIONS"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handleLoad</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">req</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fromJson</span><span class="p">(</span><span class="n">readBody</span><span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="na">getRequestBody</span><span class="p">()),</span><span class="w"/><span class="n">ColumnInfoRequest</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">vis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">store</span><span class="p">.</span><span class="na">load</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">userId</span><span class="p">(),</span><span class="w"/><span class="n">req</span><span class="p">.</span><span class="na">viewId</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">writeJson</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span><span class="w"/><span class="n">OK</span><span class="p">,</span><span class="w"/><span class="n">toJson</span><span class="p">(</span><span class="n">vis</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">Map</span><span class="p">.</span><span class="na">of</span><span class="p">()</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">vis</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In addition, there are two specialized variants that separate the editing operations from each other. The<code>ColumnVisibilitySingleHandler</code> handles PUT and DELETE requests for individual columns. It takes a JSON object with user ID, view ID, and column name and updates the persistence accordingly:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handleSingleEdit</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">req</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fromJson</span><span class="p">(</span><span class="n">readBody</span><span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="na">getRequestBody</span><span class="p">()),</span><span class="w"/><span class="n">ColumnSingleEditRequest</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">visibility</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Map</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">columnKey</span><span class="p">(),</span><span class="w"/><span class="n">req</span><span class="p">.</span><span class="na">visible</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">store</span><span class="p">.</span><span class="na">saveColumnVisibilities</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">userId</span><span class="p">(),</span><span class="w"/><span class="n">req</span><span class="p">.</span><span class="na">viewId</span><span class="p">(),</span><span class="w"/><span class="n">visibility</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">writeJson</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span><span class="w"/><span class="n">OK</span><span class="p">,</span><span class="w"/><span class="n">toJson</span><span class="p">(</span><span class="n">Map</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="s">"status"</span><span class="p">,</span><span class="w"/><span class="s">"ok"</span><span class="p">)));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>For bulk uploads, the<code>ColumnVisibilityBulkHandler</code> is used. This variant allows you to update multiple column states in a single request at the same time. The concept of bulk processing reduces latency and minimizes server-side write operations, for example, if the user changes many checkboxes in the dialog:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handleBulkEdit</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">req</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fromJson</span><span class="p">(</span><span class="n">readBody</span><span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="na">getRequestBody</span><span class="p">()),</span><span class="w"/><span class="n">ColumnEditRequest</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">store</span><span class="p">.</span><span class="na">saveColumnVisibilities</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">userId</span><span class="p">(),</span><span class="w"/><span class="n">req</span><span class="p">.</span><span class="na">viewId</span><span class="p">(),</span><span class="w"/><span class="n">req</span><span class="p">.</span><span class="na">changes</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">writeJson</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span><span class="w"/><span class="n">OK</span><span class="p">,</span><span class="w"/><span class="n">toJson</span><span class="p">(</span><span class="n">Map</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="s">"status"</span><span class="p">,</span><span class="w"/><span class="s">"ok"</span><span class="p">)));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>All three handlers follow the same design pattern: they validate the request, call the appropriate methods of the<code>PreferencesStore</code> and send back the correct HTTP status code. If successful, the server will respond with<code>200 OK</code> or<code>204 No Content</code>, and if the input is incorrect, it will respond with<code>400 Bad Request</code>. This convention makes the interface robust and easy to test.</p><p>The new API thus forms the backbone of the personalization logic. At the same time, it is an example of the loose coupling between UI and persistence: the client knows nothing about the storage strategy, the server nothing about the representation. Both communicate via well-defined JSON structures. This principle keeps the application extensible and independent of concrete implementation details – be it an in-memory store or long-term storage in EclipseStore.</p><h2 id="the-preferencesclient-round-trip-between-ui-and-server">The PreferencesClient: Round trip between UI and server</h2><p>The connection between the user interface and persistence is established by the PreferencesClient. This component takes on the task of addressing the REST endpoints of the server side via HTTP, serializing data and converting the results into structures that can be used within the Vaadin UI. The client thus acts as an intermediary between the interaction logic of the interface and the data storage of the server – a classic link between display and state.</p><p>The structure of the PreferencesClient follows the established pattern of the project&rsquo;s other service clients. It uses the Java standard library with<code>HttpClient</code>,<code>HttpRequest</code> and<code>HttpResponse</code> and completely dispenses with external dependencies. Communication takes place via clearly defined endpoints, all of which are documented in the class:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * Client for server-side column visibility preferences.</span></span></span><span class="line"><span class="cl"><span class="cm"> *</span></span></span><span class="line"><span class="cl"><span class="cm"> * Endpoints:</span></span></span><span class="line"><span class="cl"><span class="cm"> * - POST /admin/preferences/columns -&gt; load</span></span></span><span class="line"><span class="cl"><span class="cm"> * - DELETE /admin/preferences/columns -&gt; delete all (for a view)</span></span></span><span class="line"><span class="cl"><span class="cm"> * - PUT /admin/preferences/columns/edit -&gt; bulk edit</span></span></span><span class="line"><span class="cl"><span class="cm"> * - PUT /admin/preferences/columns/single -&gt; single edit</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">ColumnVisibilityClient</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span></span></span></code></pre></div></div><p>The client provides a set of methods that directly correspond to the server-side REST endpoints. The typical flow of a round trip starts with loading the stored visibility information:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="o">&gt;</span><span class="w"/><span class="nf">load</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="p">,</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">reqDto</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ColumnInfoRequest</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">viewId</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">req</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">requestBuilder</span><span class="p">(</span><span class="n">PATH_ADMIN_PREFERENCES_COLUMNS</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="w"/><span class="n">POST</span><span class="p">(</span><span class="n">jsonBody</span><span class="p">(</span><span class="n">reqDto</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">resp</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">http</span><span class="p">.</span><span class="na">send</span><span class="p">(</span><span class="n">req</span><span class="p">,</span><span class="w"/><span class="n">HttpResponse</span><span class="p">.</span><span class="na">BodyHandlers</span><span class="p">.</span><span class="na">ofString</span><span class="p">(</span><span class="n">UTF_8</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">resp</span><span class="p">.</span><span class="na">statusCode</span><span class="p">()</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">200</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">resp</span><span class="p">.</span><span class="na">body</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">body</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">body</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Collections</span><span class="p">.</span><span class="na">emptyMap</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">parsed</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">parseJson</span><span class="p">(</span><span class="n">body</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">parsed</span><span class="p">.</span><span class="na">entrySet</span><span class="p">().</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">collect</span><span class="p">(</span><span class="n">Collectors</span><span class="p">.</span><span class="na">toMap</span><span class="p">(</span><span class="n">Map</span><span class="p">.</span><span class="na">Entry</span><span class="p">::</span><span class="n">getKey</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">Boolean</span><span class="p">.</span><span class="na">parseBoolean</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="na">getValue</span><span class="p">())));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">resp</span><span class="p">.</span><span class="na">statusCode</span><span class="p">()</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">204</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Collections</span><span class="p">.</span><span class="na">emptyMap</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Unexpected HTTP "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">resp</span><span class="p">.</span><span class="na">statusCode</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" while loading column visibilities: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">resp</span><span class="p">.</span><span class="na">body</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The method generates a simple JSON object from the user and view IDs and sends it to the server as a POST request. The answer contains a map of column names to truth values that can be used directly in the UI. Missing values are interpreted as<code>true</code> , which is in line with the principle of full visibility.</p><p>The client offers two variants for changes: the editing of individual columns and the bulk update. While the<code>editSingle</code> method specifically adjusts a column state,<code>editBulk</code> allows you to commit multiple changes in a single request. This separation corresponds to the semantic structure of the REST handlers:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">editSingle</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">columnKey</span><span class="p">,</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">visible</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="p">,</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">reqDto</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ColumnSingleEditRequest</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">viewId</span><span class="p">,</span><span class="w"/><span class="n">columnKey</span><span class="p">,</span><span class="w"/><span class="n">visible</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">req</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">requestBuilder</span><span class="p">(</span><span class="n">PATH_ADMIN_PREFERENCES_COLUMNS_SINGLE</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="w"/><span class="n">PUT</span><span class="p">(</span><span class="n">jsonBody</span><span class="p">(</span><span class="n">reqDto</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">resp</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">http</span><span class="p">.</span><span class="na">send</span><span class="p">(</span><span class="n">req</span><span class="p">,</span><span class="w"/><span class="n">HttpResponse</span><span class="p">.</span><span class="na">BodyHandlers</span><span class="p">.</span><span class="na">ofString</span><span class="p">(</span><span class="n">UTF_8</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">resp</span><span class="p">.</span><span class="na">statusCode</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="n">200</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Unexpected HTTP "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">resp</span><span class="p">.</span><span class="na">statusCode</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" on single edit: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">resp</span><span class="p">.</span><span class="na">body</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This method illustrates the principle of idempotency: multiple calls with the same data result in the same state without producing side effects. This is especially important for reactive user interfaces that update states asynchronously.</p><p>In combination with the ColumnVisibilityService, which acts as a wrapper, this creates a clear communication flow:</p><ol><li>The UI interacts with the service via the ColumnVisibilityDialog.</li><li>The service calls the appropriate endpoints via the PreferencesClient.</li><li>The server side validates, stores and returns current states.</li><li>The service reflects the new values in the grid.</li></ol><p>The entire system is therefore deterministic, comprehensible and fault-tolerant. If a transfer fails, the previous state is retained and the user can initiate the operation again. The PreferencesClient thus forms the technical basis for the stable persistence of visual preferences – a simple but extremely robust bridge between interaction and storage.</p><h2 id="persistence-and-eclipsestore-integration">Persistence and EclipseStore integration</h2><p>The storage of user preferences for column visibility is done in the same persistence system that was already introduced in the previous parts of the project: EclipseStore. This decision not only follows the consistency of the architecture design, but also underlines the goal of bringing together all system states – whether functional or visual – in a coherent data model. The persistence of user preferences thus becomes an equal part of the application.</p><p>The implementation of the PreferencesStore interface plays a central role in this. There are several concrete variants in the architecture: an in-memory version for tests and volatile scenarios as well as an EclipseStore-supported version for productive operation. Both follow the same contract model, but differ in the underlying storage medium.</p><p>The following excerpt shows the interface structure on which the implementations are based:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">com</span><span class="p">.</span><span class="na">svenruppert</span><span class="p">.</span><span class="na">urlshortener</span><span class="p">.</span><span class="na">api</span><span class="p">.</span><span class="na">store</span><span class="p">.</span><span class="na">preferences</span><span class="p">.</span><span class="na">PreferencesStore</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">PreferencesStore</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">PreferencesLookup</span><span class="p">,</span><span class="w"/><span class="n">PreferencesUpdater</span><span class="p">,</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">com</span><span class="p">.</span><span class="na">svenruppert</span><span class="p">.</span><span class="na">urlshortener</span><span class="p">.</span><span class="na">api</span><span class="p">.</span><span class="na">store</span><span class="p">.</span><span class="na">preferences</span><span class="p">.</span><span class="na">PreferencesLookup</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">PreferencesLookup</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="o">&gt;</span><span class="w"/><span class="nf">load</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">com</span><span class="p">.</span><span class="na">svenruppert</span><span class="p">.</span><span class="na">urlshortener</span><span class="p">.</span><span class="na">api</span><span class="p">.</span><span class="na">store</span><span class="p">.</span><span class="na">preferences</span><span class="p">.</span><span class="na">PreferencesUpdater</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">PreferencesUpdater</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">saveColumnVisibilities</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">,</span><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="o">&gt;</span><span class="w"/><span class="n">visibility</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">delete</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">columnKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">delete</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">delete</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>These clear contracts form the basis for various storage strategies. In particular, the EclipseStore variant (<code>EclipsePreferencesStore</code>) integrates seamlessly into the existing object graph. When saving, it checks whether an entry for the combination of user and view ID already exists and updates the visibility information accordingly. A new entry is only created if no previous state exists. This logic ensures idempotency and prevents unnecessary duplicates.</p><p>The following fragment from the EclipseStore implementation illustrates the principle:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">com</span><span class="p">.</span><span class="na">svenruppert</span><span class="p">.</span><span class="na">urlshortener</span><span class="p">.</span><span class="na">api</span><span class="p">.</span><span class="na">store</span><span class="p">.</span><span class="na">provider</span><span class="p">.</span><span class="na">eclipsestore</span><span class="p">.</span><span class="na">patitions</span><span class="p">.</span><span class="na">EclipsePreferencesStore</span><span class="w"/><span class="p">(</span><span class="n">simplified</span><span class="w"/><span class="n">excerpt</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">saveColumnVisibilities</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">viewId</span><span class="p">,</span><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="o">&gt;</span><span class="w"/><span class="n">visibility</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">userPrefs</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">dataRoot</span><span class="p">.</span><span class="na">preferences</span><span class="p">().</span><span class="na">computeIfAbsent</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">viewPrefs</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">userPrefs</span><span class="p">.</span><span class="na">computeIfAbsent</span><span class="p">(</span><span class="n">viewId</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">viewPrefs</span><span class="p">.</span><span class="na">putAll</span><span class="p">(</span><span class="n">visibility</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">storage</span><span class="p">.</span><span class="na">storeRoot</span><span class="p">(</span><span class="n">dataRoot</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This method illustrates the simplicity and directness of persistence: All preferences are stored in the root object, so they automatically become part of the transactional storage in the EclipseStore. The entire system benefits from object persistence without classic database tables or ORM layers.</p><p>A key feature of this solution is its resilience. Because EclipseStore automatically synchronizes changes, user preferences are reliably maintained even if the system terminates unexpectedly. In addition, the application benefits from the direct object addressing that EclipseStore offers: no complicated ORM mappings are necessary, and changes to the data structure are automatically applied. Storage thus remains as natural as the modeling itself.</p><p>In addition, the persistence layer remains extensible. By separating the interface and implementation, alternative storage mechanisms can also be used in the future – such as an encrypted file variant for particularly sensitive environments or a network-based solution for multi-user systems. The existing code of the UI and the server handler would not have to be adapted for this, as all access takes place via the<code>PreferencesStore</code> interface.</p><p>With this integration, the persistence path is complete: from the user to the dialog, the service, the client, the REST interfaces and finally to the EclipseStore, the data flow is consistently typed and consistent. This finally makes column visibility a part of the permanent system state – a visible sign that user interaction and data storage are no longer separate worlds in this architecture, but two perspectives on the same, persistent context.</p><h2 id="architecture-and-event-flow">Architecture and Event Flow</h2><p>The introduction of dynamic column visibility has not only spawned new UI and server components, but has also refined the entirety of the application architecture. It has created a consistent flow of events that aligns all levels, from the user interface down to persistence. This consistency makes the function not only robust, but also expandable and testable.</p><p>At the center of this flow is the OverviewView as the trigger for user interaction. When the user presses the gear icon, the<code>ColumnVisibilityDialog</code>opens, which in turn communicates via the<code>ColumnVisibilityService</code>. This service calls the<code>ColumnVisibilityClient</code>, which in turn calls the server&rsquo;s REST endpoints. The server processes the request via the appropriate handlers – such as<code>ColumnVisibilityHandler</code>,<code>ColumnVisibilitySingleHandler</code> or<code>ColumnVisibilityBulkHandler</code> – and writes the changes to the EclipseStore via the<code>PreferencesStore</code>. Finally, the return channel ensures that the stored visibility is automatically restored during the next initialization.</p><p>The sequence can be schematically represented as follows:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Users</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">OverviewView</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">ColumnVisibilityDialog</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">ColumnVisibilityService</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="err">→</span><span class="w"/><span class="n">ColumnVisibilityClient</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">REST</span><span class="w"/><span class="n">API</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">PreferencesHandler</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="err">→</span><span class="w"/><span class="n">PreferencesStore</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">EclipseStore</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">Persistent</span><span class="w"/><span class="n">Storage</span></span></span></code></pre></div></div><p>Each of these components fulfils a clearly defined role and communicates exclusively via well-defined interfaces. In this way, the architecture strictly follows the principle of separation of concerns. The UI part remains completely decoupled from persistence, while the server logic does not require any knowledge of the frontend. Changes to one layer do not have an immediate effect on the other layers.</p><p>A particularly elegant aspect can be seen in the interaction between the user interface and the event system. The OverviewView is connected to the<code>StoreEvents</code> event system . As soon as changes to the database are detected on the server side, a signal is sent to the UI via a publish/subscribe pattern. The application responds to this with a synchronized refresh:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">subscription</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">StoreEvents</span><span class="p">.</span><span class="na">subscribe</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">getUI</span><span class="p">().</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">ui</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(</span><span class="k">this</span><span class="p">::</span><span class="n">refresh</span><span class="p">)));</span></span></span></code></pre></div></div><p>This mechanic, which is already known from the previous days of the Advent calendar, ensures that the user interface and saved state always match. It forms the foundation of reactivity throughout the project.</p><p>The event flow within the application is thus bidirectional: On the one hand, the user initiates interactions that lead through the round trip to persistence. On the other hand, persistent changes can in turn have an effect on the UI. This interplay between action and reaction creates a dynamic coherence that makes the system tangibly alive.</p><p>Compared to classic web applications, in which the state between client and server is often fragmented or only temporary, this architecture establishes a continuous data cycle. Every step is traceable and typified – from the checkbox in the dialog to the saved Boolean map in the EclipseStore. This minimizes sources of error and significantly increases the maintainability of the application.</p><p>All in all, it can be seen that the addition of dynamic column visibility is not just a new function, but a maturation of the entire system architecture. It combines UI, event control and persistence into a coherent whole and thus lays the foundation for further personalization mechanisms based on the same principles.</p><h2 id="ux-and-ergonomics-self-determined-work">UX and ergonomics: Self-determined work</h2><p>With the introduction of dynamic column visibility, the relationship between user and application is fundamentally changing. While the previous functions were primarily aimed at consistency, stability and clarity, one aspect is now coming to the fore that was previously mainly implicit: the self-determination of the user. The OverviewView interface is no longer a rigid reflection of developers&rsquo; decisions, but becomes a customizable tool that is subordinate to the individual ways of working of its users.</p><p>This change is already evident in the way the dialogue has been shaped. The<code>ColumnVisibilityDialog</code> follows the principle of minimal friction: the user should be able to adjust his view without losing context or feeling cognitive load. Therefore, the dialog opens modally, focuses on the current task and offers only those controls that are relevant for the decision. Each checkbox represents a column – nothing more, nothing less. The immediate feedback in the grid after each change ensures that the user experiences the effects of their decision directly.</p><p>The combination of immediate feedback and persistent storage has a psychologically important effect: it creates trust. When a system visibly responds to the user&rsquo;s preferences and restores them unchanged the next time it is opened, the feeling of stability and control is created. This is one of the cornerstones of good interaction design – the system should not only work, but also appear reliable.</p><p>The placement of the function on the surface also follows ergonomic considerations. The gear icon that opens the dialog is deliberately designed to be unobtrusive. It is accessible at all times, but not dominant. This keeps the focus on the content while keeping control over the presentation always within reach. The user decides for himself when and to what extent he makes adjustments. The system does not impose itself, but reacts.</p><p>Combined with the automatic restoration of preferences at launch, it creates a gentle personalization that feels organic. The application not only remembers states – it also remembers habits. If you have hidden certain columns, you signal a personal usage pattern that will be quietly respected on your next visit. This form of implicit personalization is efficient because it does not require additional dialogues, profile settings or user accounts and still creates an individual experience.</p><p>The developer perspective is not left out. The architecture was designed in such a way that the expansion of further personal settings – such as sort orders, column widths or layout preferences – would be possible seamlessly. The existing concept of the<code>PreferencesStore</code> allows the inclusion of additional parameters without structural changes. This creates an extensible system that may seem simple in the user experience, but is highly modular in the background.</p><p>This makes the dynamic column visibility feature more than just a convenience feature. It is an expression of a philosophy: the user should be able to find himself in the application. Software that adapts instead of demanding adaptation creates acceptance and productivity at the same time. The user remains the focus, technology takes a back seat – exactly where it has its greatest effect.</p><h2 id="technical-reflection-and-safety-aspects">Technical reflection and safety aspects</h2><p>The introduction of dynamic column visibility is not only a functional extension, but also a technical statement. It shows that personalization in a modern web application does not have to be at odds with robustness, security, and maintainability. Rather, individualization can be integrated as a controlled and comprehensible extension of the existing security model.</p><p>From a technical point of view, the new function touches on several security-relevant levels. First of all, the handling of user IDs should be mentioned. In the current implementation, the username &ldquo;admin&rdquo; serves as a placeholder, but in a production environment, this information comes from an authenticated context. It is important that the preferences are strictly tied to the authenticated user in order to ensure a clear delimitation of the visibility areas. A user may only access their own stored preferences – never anyone else&rsquo;s.</p><p>The interfaces themselves are designed in such a way that they already implicitly assume this separation. Each request contains both a<code>userId</code> and a<code>viewId</code>. The REST handlers check that both values are present and valid. Missing or empty fields will result in a controlled rejection of the request:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isBlank</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">userId</span><span class="p">())</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">isBlank</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">viewId</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">writeJson</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span><span class="w"/><span class="n">BAD_REQUEST</span><span class="p">,</span><span class="w"/><span class="s">"userId and viewId required"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This simple but effective validation protects against non-specific calls and prevents unwanted manipulation of the database. In combination with the REST semantics, which only allow idempotent operations (POST, PUT, DELETE), it ensures that the application behaves deterministically even under heavy load or during repeated requests.</p><p>Another important aspect concerns data integrity within the<code>PreferencesStore</code>. Since the stored values are organized in a map&lt;string, Boolean&gt; the question arises as to how to deal with invalid or manipulated data. The implementation uses type checking and defensive programming for this purpose: Only known column names are adopted, unknown or incorrect keys are discarded. This keeps the persisted state consistent, even if a failed client sends data.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"/><span class="n">knownKeys</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getColumns</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">Grid</span><span class="p">.</span><span class="na">Column</span><span class="p">::</span><span class="n">getKey</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">Objects</span><span class="p">::</span><span class="n">nonNull</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">toList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="o">&gt;</span><span class="w"/><span class="n">state</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">service</span><span class="p">.</span><span class="na">mergeWithDefaults</span><span class="p">(</span><span class="n">knownKeys</span><span class="p">);</span></span></span></code></pre></div></div><p>This logic in the UI prevents unauthorized or inappropriate values from being stored in the first place. The dialog works exclusively with those columns that actually exist in the grid, and is therefore inherently resistant to manipulation attempts on the client side.</p><p>In addition to functional safety, resilience to errors also plays an essential role. All communication paths between client and server are embedded in<code>try-catch</code> blocks. If the connection is lost or the server does not respond, the application will continue to be usable. The service reports errors via logging without blocking user interaction:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">client</span><span class="p">.</span><span class="na">editBulk</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">viewId</span><span class="p">,</span><span class="w"/><span class="n">changes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">warn</span><span class="p">(</span><span class="s">"Persist bulk failed {}: {}"</span><span class="p">,</span><span class="w"/><span class="n">changes</span><span class="p">.</span><span class="na">keySet</span><span class="p">(),</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">toString</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This approach ensures that server communication failures do not have a negative impact on the user experience. The user can continue working; its changes are automatically synchronized on the next successful connection.</p><p>Overall, it can be seen that the security concept of the application has not been weakened by the expansion, but strengthened. The integrity of the system is maintained through clear interfaces, typed data structures, defensive programming and strict separation of user contexts. The dynamic column visibility proves that user freedom and system security are not mutually exclusive, but – if implemented carefully – reinforce each other.</p><h2 id="result">Result</h2><p>With the introduction of dynamic column visibility, the development of the administration interface has reached a decisive milestone. What began as a static, system-centric overview in the first days of the Advent calendar has now developed into an interactive, user-centered platform. The application learns, reacts and remembers – it becomes a tool that adapts to the user instead of forcing him to get used to its structures.</p><p>Instead of replacing existing components, they have been expanded and precisely linked to each other. The new<code>ColumnVisibilityDialog</code> fits seamlessly into the existing UI concept, the<code>PreferencesClient</code> uses the same mechanisms as the previous REST clients, and persistence via the<code>EclipseStore</code> closes the circle of dataflows. Everything interlocks, without breaks or special paths. This homogeneity is not a coincidence, but the result of an architecture that has anchored openness and coherence as fundamental principles from the very beginning.</p><p>From a user experience perspective, the extension is a step towards self-determination. Users can now personalize their workspace without having to struggle through menus and options. The interface remains light, the behavior is comprehensible and the stored preferences create trust in the stability of the application. The system reacts to user decisions instead of forcing them – a paradigm shift that noticeably increases the quality of interaction.</p><p>The clear separation between user context, visibility logic and data storage prevents manipulation and ensures data integrity. The use of typed maps and well-defined REST endpoints minimizes sources of error. Persistence via<code>EclipseStore</code> also offers the advantage that the application remains consistent at all times, even in the event of abrupt interruptions or partial failures.</p><p>Looking ahead opens up new possibilities. On the basis of the now established preference system, further personalization dimensions can be implemented: column sequences, sorting preferences, color schemes or layout options. By loosely coupling UI, service and persistence, the system is ready for these extensions without having to change existing structures. Integration with user profiles or role-based access models could also be built on it.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Part-02-SteamPunk.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Part-02-SteamPunk.png"/><enclosure url="https://svenruppert.com/images/2025/12/Part-02-SteamPunk.png" type="image/jpeg" length="0"/></item><item><title>Advent Calendar - 2025 - ColumnVisibilityDialog - Part 1</title><link>https://svenruppert.com/posts/advent-calendar-2025-columnvisibilitydialog-part-1/</link><pubDate>Mon, 08 Dec 2025 22:42:05 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-columnvisibilitydialog-part-1/</guid><description>From observer to designer: User control at a glance With the fourth day of the Advent calendar, the perspective on the application changes fundamentally. While in the previous expansion stages users reacted primarily to prepared structures, an element of active design is now being added. The &ldquo;Overview&rdquo; has so far been a central mirror of the system state: It showed all saved short links, allowed them to be filtered and provided an in-depth insight into individual objects with the detailed dialog. But the structure and visibility of the columns were immutable up to this point. The user always saw the selection that the developer had defined as meaningful. With the introduction of dynamic column visibility, this state is removed.</description><content:encoded>&lt;![CDATA[<h2 id="from-observer-to-designer-user-control-at-a-glance">From observer to designer: User control at a glance</h2><p>With the fourth day of the Advent calendar, the perspective on the application changes fundamentally. While in the previous expansion stages users reacted primarily to prepared structures, an element of active design is now being added. The &ldquo;Overview&rdquo; has so far been a central mirror of the system state: It showed all saved short links, allowed them to be filtered and provided an in-depth insight into individual objects with the detailed dialog. But the structure and visibility of the columns were immutable up to this point. The user always saw the selection that the developer had defined as meaningful. With the introduction of dynamic column visibility, this state is removed.</p><ol><li><a href="https://svenruppert.com/2025/12/08/advent-calendar-2025-columnvisibilitydialog-part-1/#from-observer-to-designer-user-control-at-a-glance">From observer to designer: User control at a glance</a></li><li><a href="https://svenruppert.com/2025/12/08/advent-calendar-2025-columnvisibilitydialog-part-1/#concept-visibility-as-a-user-preference">Concept: Visibility as a user preference</a></li><li><a href="https://svenruppert.com/2025/12/08/advent-calendar-2025-columnvisibilitydialog-part-1/#the-new-ui-interaction-columnvisibilitydialog">The new UI interaction: ColumnVisibilityDialog</a></li><li><a href="https://svenruppert.com/2025/12/08/advent-calendar-2025-columnvisibilitydialog-part-1/#integration-with-the-overviewview">Integration with the OverviewView</a></li></ol><p><strong>The source code for this version can be found on GitHub at</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-04">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-04</a></p><p>Here&rsquo;s the screenshot of the version we&rsquo;re implementing now.</p><p><figure><img src="/images/2025/12/image-16.png" alt="Screenshot of the URL Shortener application Overview page, displaying a table with shortcodes, URLs, creation dates, and expiration status, along with search and filter options." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-17.png" alt="Screenshot of the URL Shortener Overview interface displaying fields for searching shortcodes and URLs, a grid showing short links with their details, and a dialog for column visibility options." loading="lazy" decoding="async"/></p><p>The grid in the OverviewView is thus transformed from a static display element into a customizable tool. Users can choose which columns are visible and which are hidden. This step seems minor at first glance, but it leads to a new form of interaction between user and application. The system no longer determines the surface structure, but reacts to individual preferences. The application enters into a dialogue with the user, in which the view of data becomes part of the personalization.</p><p>This extension is a direct continuation of the basis laid in the previous days. The filter and search mechanisms from the first part have taken control of the data flow. The integration of EclipseStore has made the state visible and at the same time established permanent storage in the system. Finally, the detail dialog created the separation between the overview and the object context. With this in mind, the new feature is the next logical step: it shifts control over the presentation of data from the system to the user without complicating the underlying technical structure.</p><p>From a user experience perspective, this change is significant. Those who work with the administration interface on a daily basis develop their own rhythm in dealing with data. Some users focus on expiring links, others on manual aliases, or on the traffic of their short links. For all these scenarios, a static column arrangement is a hindrance. The ability to hide irrelevant information and tailor the view to your own work processes creates not only convenience, but also efficiency. The user thus turns from a passive observer into an active designer of his or her work environment.</p><p>The implementation of this principle in Vaadin is based on a conscious balance between simplicity and persistence. On the one hand, the function should be immediately available: One click opens a dialog, a handful of checkboxes controls the visibility of the columns, and the result becomes immediately visible. On the other hand, these attitudes should not remain fleeting. The selected configuration is permanently saved and automatically restored on each reboot. This persistence in visual behavior takes up the idea of the visible system state and transfers it to the user level.</p><p>The introduction of dynamic column visibility thus marks the beginning of a new chapter in the development of the administration interface: It is no longer just about the presentation of data, but about the design of one&rsquo;s own work context. This feature marks a paradigm shift in the UI design of the project. Instead of predefined structures, a flexible, learning interface is created that adapts to individual ways of working and thus takes a decisive step towards true user-centricity.</p><h2 id="concept-visibility-as-a-user-preference">Concept: Visibility as a user preference</h2><p>The introduction of dynamic column visibility not only marks an ergonomic improvement in the user interface, but also a conceptual expansion of the application architecture. For the first time, an aspect of user interaction is permanently personalized – regardless of the session or system state. This innovation requires a separate model for preferences that works consistently on both the client and server side. Visibility thus becomes a stored property that is mediated between the user and the application.</p><p>The central idea is that each view in the application has its own identity. For the OverviewView, this means that its configuration – such as which columns are visible – is clearly stored under a view ID. At the same time, each user instance is identified, for example by means of a user ID, and treated as a carrier of individual preferences. These two dimensions – user and view – are key to storing visibility information. The concept thus follows a clear scheme: a user ID, a view ID and an assignment of column names to truth values.</p><p>The introduction of this logic required a new interface between the UI and the backend. The application had to learn to deal with semantic information about user decisions, not just data objects. On the server side, this is done by the new PreferencesHandler, which serves as a REST endpoint and allows both loading and saving the settings. On the client side, the new PreferencesClient component communicates with this handler and manages the transfer of the data structures. The connecting format is deliberately simple: a map&lt;string, Boolean&gt; in which each key corresponds to the column name and the value describes the visibility. This clarity makes it possible to keep the mechanism generic and later to extend it to other areas of the application.</p><p>The decision to model preferences as a separate entity is an important architectural step. It separates application data—such as short links and expiration information—from the presentation data that drives the user&rsquo;s behavior within the interface. In this way, the persistence layer is expanded by a new category: no longer only technical but also contextual states. This pattern is reminiscent of the separation between domain logic and UI logic, which is central to the clean architecture approach, and consistently transfers it to the behavior of the user interface.</p><p>Another goal of the implementation is transparency. The preferences should not act as a black box, but remain traceable at all times. Changes to the column configuration are therefore immediately visible and saved at the same time. The user does not feel a break between the interaction and the result, and yet their behavior is preserved permanently. This dual nature – volatile response and persistent storage – makes the system a learning tool. The application remembers how its users work and automatically reproduces this behavior on the next call.</p><p>This adds a semantic layer to the UI concept: visibility is no longer understood as a technical property of the grid component, but as an expression of an individual way of working. The application itself becomes a customizable medium that adapts to people, not the other way around. In this perspective, the dynamics of column visibility become a symbol for the transition from standardized operation to personalized control – a principle that will also be reflected in the technical implementation in the following chapters.</p><h2 id="the-new-ui-interaction-columnvisibilitydialog">The new UI interaction: ColumnVisibilityDialog</h2><p>With the introduction of dynamic column visibility, the application&rsquo;s user interface gets a new level of interaction. The previously immutable structure of the OverviewView is extended by a component that allows the user to directly influence the structure of his working environment. At the heart of this is the<strong>ColumnVisibilityDialog</strong> – a dialog box that lists all available columns in the overview table and controls their visibility via simple checkboxes. It embodies the idea of a configurable but intuitive interface.</p><p>The concept of dialogue follows a deliberate reduction: no cluttered settings menu, no hidden options, but a clear, focused place for a single task. When the dialog is opened, the application reads the saved preferences of the current user via the<strong>PreferencesClient</strong> and sets the checkboxes according to the stored values. Each checkbox represents a column of the OverviewView – such as &ldquo;Shortcode&rdquo;, &ldquo;URL&rdquo;, &ldquo;Created&rdquo; or &ldquo;Expires&rdquo;. Changes are immediately visible: As soon as the user selects or deselect a checkbox, the corresponding column in the grid is shown or hidden. This creates a direct interplay between action and reaction, which strengthens the perception of control.</p><p>The ColumnVisibilityDialog is deliberately implemented as an independent component. It extends the Vaadin class Dialog and follows the same architectural philosophy as the already known DetailsDialog from the previous days. This parallel allows it to fit seamlessly into the existing structure: it has a clear lifecycle logic, its own events and a minimalist layout that is designed for comprehensibility and efficiency. The dialog typically contains a vertical list of checkboxes that is dynamically generated from the grid&rsquo;s column configuration. A save button permanently updates preferences through the PreferencesClient, while a &ldquo;Reset&rdquo; button restores the original default view.</p><p>From a technical point of view, the dialog is based on a simple but elegant data flow. When opened, the client loads a map&lt;string, Boolean&gt; whose keys correspond to the column identifiers. This map is converted into UI elements, with the current visibility mirrored directly in the grid. Clicking on &ldquo;Save&rdquo; triggers the method<strong>saveColumnVisibilities(userId, viewId, visibilityMap),</strong> which transmits the changed settings to the server. The JSON format is deliberately left flat to be both human-readable and easily testable. Server-side storage in EclipseStore ensures that these changes are not only retained for the current session, but also permanently.</p><p>The interplay of dialog, grid and client shows the strength of the modular approach. The ColumnVisibilityDialog does not know any details about persistence or grid implementation – it works exclusively via clearly defined interfaces. This keeps the component independent, reusable, and easily expandable. This pattern is particularly important in the Vaadin architecture because it ensures the loose coupling between user interaction and system health.</p><p>From a user experience perspective, the dialog performs two tasks at the same time. It provides control without complexity and creates confidence in the stability of the system. Users experience that their individual decisions are not only temporary, but are structurally anchored in the application. Every adjustment is reproducible, every change is reversible. This creates a natural balance between freedom and security, which reflects the character of the entire project. The ColumnVisibilityDialog thus becomes a visible expression of the idea that an administration interface should not only provide tools, but also adapt to the way its users work.</p><p>To illustrate this, the following is an original excerpt from the dialog, which shows the coupling between checkboxes and grid columns as well as the collection of changes for an optional collective persistence. The code works directly on the column keys of the grid and reflects state changes back to the interface without any detours.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="o">&gt;</span><span class="w"/><span class="n">pending</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">LinkedHashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="na">getColumns</span><span class="p">().</span><span class="na">forEach</span><span class="p">(</span><span class="n">col</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">key</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">col</span><span class="p">.</span><span class="na">getKey</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">key</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/><span class="n">only</span><span class="w"/><span class="n">addressable</span><span class="w"/><span class="n">columns</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">visible</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">state</span><span class="p">.</span><span class="na">getOrDefault</span><span class="p">(</span><span class="n">key</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">col</span><span class="p">.</span><span class="na">setVisible</span><span class="p">(</span><span class="n">visible</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">cb</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Checkbox</span><span class="p">(</span><span class="n">col</span><span class="p">.</span><span class="na">getHeaderText</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">col</span><span class="p">.</span><span class="na">getHeaderText</span><span class="p">()</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">visible</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">cb</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">v</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Boolean</span><span class="p">.</span><span class="na">TRUE</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">ev</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">col</span><span class="p">.</span><span class="na">setVisible</span><span class="p">(</span><span class="n">v</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pending</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">v</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">form</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">cb</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">var</span><span class="w"/><span class="n">btnApply</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Apply bulk"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">pending</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">service</span><span class="p">.</span><span class="na">setBulk</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">LinkedHashMap</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">pending</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pending</span><span class="p">.</span><span class="na">clear</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnApply</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">);</span></span></span></code></pre></div></div><p>The dialog deliberately delegates the actual persistence to the service. This node abstracts the transport and writes the changed visibilities to the server in one go. The following fragment shows the corresponding method in the service, which encapsulates the round trip and at the same time remains fault-tolerant.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setBulk</span><span class="p">(</span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Boolean</span><span class="o">&gt;</span><span class="w"/><span class="n">changes</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">changes</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">changes</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">client</span><span class="p">.</span><span class="na">editBulk</span><span class="p">(</span><span class="n">userId</span><span class="p">,</span><span class="w"/><span class="n">viewId</span><span class="p">,</span><span class="w"/><span class="n">changes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">warn</span><span class="p">(</span><span class="s">"Persist bulk failed {}: {}"</span><span class="p">,</span><span class="w"/><span class="n">changes</span><span class="p">.</span><span class="na">keySet</span><span class="p">(),</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">toString</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this way, the ColumnVisibilityDialog remains lean and focused on the interaction, while the persistence logic is handled centrally via the service and the underlying client. This division of responsibilities ensures that the user experience feels clear and responsive without compromising the technical integrity of the application.</p><h2 id="integration-with-the-overviewview">Integration with the OverviewView</h2><p>The introduction of the ColumnVisibilityDialog would have remained incomplete if it had not been organically embedded in the existing OverviewView. This is exactly where the strength of the architecture becomes apparent, which was designed for loose coupling and clear responsibilities from the very beginning. The OverviewView serves as the application&rsquo;s central window—connecting data, filters, paging, and interaction. Now it is expanding this spectrum to include a persistent personalization dimension.</p><p>At the center of the integration is an inconspicuous but meaningful button in the search bar: the gear icon. This icon acts as an entry point into the configuration logic. The decision not to hide the dialog in a menu or in a separate view follows the idea of control available at all times. Users should be able to customize the presentation of the data exactly where they look at it. The code snippet from the OverviewView illustrates this direct integration:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">btnSettings</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_TERTIARY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnSettings</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="s">"Column visibility"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">btnSettings</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ColumnVisibilityDialog</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">grid</span><span class="p">,</span><span class="w"/><span class="n">columnVisibilityService</span><span class="p">).</span><span class="na">open</span><span class="p">());</span></span></span></code></pre></div></div><p>With just a few lines, the entire functionality is connected. The button opens a new instance of the dialog, which receives the current grid and the corresponding service instance. This service instance manages all operations on the stored preferences. In this way, a clear data flow is created: the OverviewView calls the dialog, the dialog interacts with the ColumnVisibilityService, and the ColumnVisibilityService in turn communicates with the server via the ColumnVisibilityClient.</p><p>The initialization of the service takes place when the view is attached to the UI. This phase is ideal for loading user contexts and creating initial states. The relevant code block from the<code>onAttach</code> method shows this flow:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">protected</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">onAttach</span><span class="p">(</span><span class="n">AttachEvent</span><span class="w"/><span class="n">attachEvent</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">super</span><span class="p">.</span><span class="na">onAttach</span><span class="p">(</span><span class="n">attachEvent</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">columnVisibilityService</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ColumnVisibilityService</span><span class="p">(</span><span class="n">columnVisibilityClient</span><span class="p">,</span><span class="w"/><span class="s">"admin"</span><span class="p">,</span><span class="w"/><span class="s">"overview"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">keys</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getColumns</span><span class="p">().</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">Grid</span><span class="p">.</span><span class="na">Column</span><span class="p">::</span><span class="n">getKey</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">Objects</span><span class="p">::</span><span class="n">nonNull</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">toList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">vis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">columnVisibilityService</span><span class="p">.</span><span class="na">mergeWithDefaults</span><span class="p">(</span><span class="n">keys</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">getColumns</span><span class="p">().</span><span class="na">forEach</span><span class="p">(</span><span class="n">c</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">k</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">c</span><span class="p">.</span><span class="na">getKey</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">k</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="n">c</span><span class="p">.</span><span class="na">setVisible</span><span class="p">(</span><span class="n">vis</span><span class="p">.</span><span class="na">getOrDefault</span><span class="p">(</span><span class="n">k</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">subscription</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">StoreEvents</span><span class="p">.</span><span class="na">subscribe</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">getUI</span><span class="p">().</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">ui</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(</span><span class="k">this</span><span class="p">::</span><span class="n">refresh</span><span class="p">)));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>When setting up the view, the service is first created and linked to the user and view ID. All known column names are then determined and their visibility is coordinated with the stored preferences. Each column will then be shown or hidden according to its saved setting. This way, the column states are not fleeting, but consistent between sessions and restarts.</p><p>This integration is deliberately implemented discreetly. The dialog is invoked only when the user becomes active, and the service automatically takes into account the saved settings. In this way, the application is not overloaded with new interactions, but its behavior is organically expanded by a context-aware memory.</p><p>One aspect of this architecture is the way it maintains the balance between control and simplicity. The user does not need to load or save any configuration – everything happens in the background. The view remains responsive, the server records the state, and the dialog acts as an interface between the two. This invisible connection between the surface and the persistence layer gives the application the lightness that keeps it intuitive and efficient despite the growing variety of functions.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Part-01-SteamPunk.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Part-01-SteamPunk.png"/><enclosure url="https://svenruppert.com/images/2025/12/Part-01-SteamPunk.png" type="image/jpeg" length="0"/></item><item><title>Advent Calendar - 2025 - Detail Dialog - Part 2</title><link>https://svenruppert.com/posts/advent-calendar-2025-detail-dialog-part-2/</link><pubDate>Sun, 07 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-detail-dialog-part-2/</guid><description>Client contract from a UI perspective In this project, the user interface not only serves as a graphical layer on top of the backend, but is also part of the overall contract between the user, the client, and the server. This part focuses on the data flow from the UI&rsquo;s perspective: how inputs are translated into structured requests, how the client forwards them, and what feedback the user interface processes.</description><content:encoded>&lt;![CDATA[<h2 id="client-contract-from-a-ui-perspective">Client contract from a UI perspective</h2><p>In this project, the user interface not only serves as a graphical layer on top of the backend, but is also<strong>part of the overall contract</strong> between the user, the client, and the server. This part focuses on the data flow from the UI&rsquo;s perspective: how inputs are translated into structured requests, how the client forwards them, and what feedback the user interface processes.</p><ol><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#client-contract-from-a-ui-perspective">Client contract from a UI perspective</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#client-layer-urlshortenerclient-extensions">Client layer (URLShortenerClient): Extensions</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#server-api-and-handler">Server API and Handler</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#persistence-and-store-implementations">Persistence and Store Implementations</a><ol><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#inmemory-implementation">InMemory Implementation</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#eclipsestore-implementation">EclipseStore Implementation</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#uniformity-and-compatibility">Uniformity and Compatibility</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#advantages-of-the-approach">Advantages of the approach</a></li></ol></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#domain-model-and-defaults">Domain Model and Defaults</a><ol><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#shorturlmapping-as-a-central-link">ShortUrlMapping as a central link</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#defaultvalues-central-system-constants">DefaultValues – Central System Constants</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#aliaspolicy-with-logging">AliasPolicy with Logging</a></li></ol></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#json-serialisation-and-deserialization">JSON serialisation and deserialization</a><ol><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#extending-serialisation-in-jsonutils">Extending Serialisation in JsonUtils</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#extending-deserialization">Extending Deserialization</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#purge-of-incoming-json-data">Purge of incoming JSON data</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#optimisation-of-json-output">Optimisation of JSON output</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#consistency-and-interoperability">Consistency and interoperability</a></li></ol></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#security-and-robustness-in-the-ui-flow">Security and robustness in the UI flow</a><ol><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#input-validations-in-the-generation-dialogue">Input validations in the generation dialogue</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#defensive-navigation-in-the-detail-dialogue">Defensive navigation in the detail dialogue</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#dealing-with-the-clipboard-api">Dealing with the Clipboard API</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#consistent-feedback-and-fault-tolerance">Consistent feedback and fault tolerance</a></li></ol></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-2/#result">Result</a></li></ol><p><strong>The source code for this version can be found on GitHub at</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-03">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-03</a></p><p>Here&rsquo;s the screenshot of the version we&rsquo;re implementing now.</p><p><figure><img src="/images/2025/12/image-13-1024x391.png" alt="Screenshot of the URL Shortener overview page displaying a list of shortened URLs, including their shortcode, original URL, creation time, and expiration status." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-15-1024x712.png" alt="Screenshot of a URL shortener dashboard displaying a table of shortcodes and associated URLs. The interface includes filters for searching shortcodes and original URLs, an option for sorting, and details for each shortcode, including creation date and expiry status." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-14-1024x419.png" alt="Screenshot of a URL shortener overview page displaying shortcodes, original URLs, creation dates, expiration status, and action buttons." loading="lazy" decoding="async"/></p><p>The basis of the contract is the class ShortenRequest, which was extended in this development step with the new field expiresAt. This field serves as a central repository for expiration dates and is entirely optional – meaning existing clients will continue to function even without this attribute. The UI client is thus both backwards-compatible and future-proof.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ShortenRequest</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">shortURL</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiresAt</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="nf">ShortenRequest</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">shortURL</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiresAt</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">url</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">url</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">shortURL</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">shortURL</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">expiresAt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">expiresAt</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="nf">getExpiresAt</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">expiresAt</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setExpiresAt</span><span class="p">(</span><span class="n">Instant</span><span class="w"/><span class="n">expiresAt</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">expiresAt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">expiresAt</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The<strong>CreateView</strong> passes this object to the URLShortenerClient, which handles communication with the server. The decisive factor here is that the user interface does not establish HTTP connections directly but delegates this task to a dedicated client component. This keeps the UI lean and testable, while the client is centrally responsible for logging, error handling and request generation. The central interface is the createCustomMapping method, which maps the extended contract:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="w"/><span class="nf">createCustomMapping</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiredAt</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Create custom mapping alias='{}' url='{}' expiredAt='{}'"</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">expiredAt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">URL</span><span class="w"/><span class="n">shortenUrl</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serverBaseAdmin</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">PATH_ADMIN_SHORTEN</span><span class="p">).</span><span class="na">toURL</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">HttpURLConnection</span><span class="w"/><span class="n">connection</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">HttpURLConnection</span><span class="p">)</span><span class="w"/><span class="n">shortenUrl</span><span class="p">.</span><span class="na">openConnection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">connection</span><span class="p">.</span><span class="na">setRequestMethod</span><span class="p">(</span><span class="s">"POST"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">connection</span><span class="p">.</span><span class="na">setDoOutput</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">connection</span><span class="p">.</span><span class="na">setRequestProperty</span><span class="p">(</span><span class="n">CONTENT_TYPE</span><span class="p">,</span><span class="w"/><span class="n">JSON_CONTENT_TYPE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">var</span><span class="w"/><span class="n">shortenRequest</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ShortenRequest</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">expiredAt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">shortenRequest</span><span class="p">.</span><span class="na">toJson</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getOutputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">body</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(</span><span class="n">UTF_8</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">int</span><span class="w"/><span class="n">status</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getResponseCode</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">200</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">201</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">is</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">String</span><span class="w"/><span class="n">jsonResponse</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">String</span><span class="p">(</span><span class="n">is</span><span class="p">.</span><span class="na">readAllBytes</span><span class="p">(),</span><span class="w"/><span class="n">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">shortUrlMapping</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fromJson</span><span class="p">(</span><span class="n">jsonResponse</span><span class="p">,</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="n">shortUrlMapping</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">409</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Alias already in use"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Unexpected status: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">status</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This example illustrates how precisely the UI and the client are coordinated. The UI passes a fully populated domain instance (ShortenRequest) that contains all the required fields. The client handles the serialisation, performs the communication, and returns a ShortUrlMapping in response. The UI then displays the relevant data immediately.</p><p>A central design principle in this interaction is<strong>&ldquo;data instead of commands&rdquo;.</strong> The UI does not send specific control commands, but always sends complete data objects. The backend decides how to operate based on these objects. This decoupling has several advantages:</p><ol><li><strong>Extensibility:</strong> New fields (e.g. expiresAt) can be added without breaking existing APIs.</li><li><strong>Traceability:</strong> Every operation is fully traceable via the Request object.</li><li><strong>Security:</strong> The client can validate inputs before converting them into HTTP requests.</li></ol><p>In the UI, the client&rsquo;s responses are used to provide feedback and update the current view. Not only is the shortcode displayed, but the entire mapping object is also displayed, which already contains all the values calculated on the server side. The user interface thus always remains in line with the actual system state.</p><p>This pattern –<strong>a clear contract structure between UI, client and server</strong> – creates stability and maintainability in the long term. Changes to the backend API do not require adjustments to the UI logic, as long as the underlying data contract remains unchanged. This establishes a binding communication path that enables technical evolution without impairing user interaction.</p><h2 id="client-layer-urlshortenerclient-extensions">Client layer (URLShortenerClient): Extensions</h2><p>The client layer forms the technical bridge between the user interface and the server. It translates data objects into HTTP requests, monitors the communication, and transfers the results back into domain objects. This chapter shows how the existing class URLShortenerClient has been extended to support the new expiration concept (expiresAt) while ensuring clean, valid communication with the server.</p><p>The starting point was the existing function createCustomMapping(String alias, String url), which an overloaded variant has now supplemented. This accepts an additional expiration date (Instant expiredAt) and performs all necessary steps to transfer the data to the server in a complete and compliant manner.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="w"/><span class="nf">createCustomMapping</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiredAt</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Create custom mapping alias='{}' url='{}' expiredAt='{}'"</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">expiredAt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">alias</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">alias</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">var</span><span class="w"/><span class="n">validate</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">AliasPolicy</span><span class="p">.</span><span class="na">validate</span><span class="p">(</span><span class="n">alias</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">validate</span><span class="p">.</span><span class="na">valid</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">var</span><span class="w"/><span class="n">reason</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">validate</span><span class="p">.</span><span class="na">reason</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="n">reason</span><span class="p">.</span><span class="na">defaultMessage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">URL</span><span class="w"/><span class="n">shortenUrl</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serverBaseAdmin</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">PATH_ADMIN_SHORTEN</span><span class="p">).</span><span class="na">toURL</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">HttpURLConnection</span><span class="w"/><span class="n">connection</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">HttpURLConnection</span><span class="p">)</span><span class="w"/><span class="n">shortenUrl</span><span class="p">.</span><span class="na">openConnection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">connection</span><span class="p">.</span><span class="na">setRequestMethod</span><span class="p">(</span><span class="s">"POST"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">connection</span><span class="p">.</span><span class="na">setDoOutput</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">connection</span><span class="p">.</span><span class="na">setRequestProperty</span><span class="p">(</span><span class="n">CONTENT_TYPE</span><span class="p">,</span><span class="w"/><span class="n">JSON_CONTENT_TYPE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">var</span><span class="w"/><span class="n">shortenRequest</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ShortenRequest</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">expiredAt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">shortenRequest</span><span class="p">.</span><span class="na">toJson</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"createCustomMapping - body - '{}'"</span><span class="p">,</span><span class="w"/><span class="n">body</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getOutputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">body</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(</span><span class="n">UTF_8</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kt">int</span><span class="w"/><span class="n">status</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getResponseCode</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">200</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">201</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">is</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">String</span><span class="w"/><span class="n">jsonResponse</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">String</span><span class="p">(</span><span class="n">is</span><span class="p">.</span><span class="na">readAllBytes</span><span class="p">(),</span><span class="w"/><span class="n">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"createCustomMapping - jsonResponse - {}"</span><span class="p">,</span><span class="w"/><span class="n">jsonResponse</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">shortUrlMapping</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fromJson</span><span class="p">(</span><span class="n">jsonResponse</span><span class="p">,</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"shortUrlMapping .. {}"</span><span class="p">,</span><span class="w"/><span class="n">shortUrlMapping</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="n">shortUrlMapping</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">409</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Alias already in use"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Unexpected status: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">status</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>With this method, the extended expiration date is seamlessly integrated into the existing communication flow. The UI calls this method via CreateView, keeping the extension transparent to the user – the new functionality is immediately available without changing the user experience.</p><p>In addition to improving the method signature, the<strong>consistency of HTTP communication</strong> has also been improved. Instead of manually setting headers, the constant JSON_CONTENT_TYPE is now used consistently to avoid format errors and ensure unique typing. This standardisation reduces the risk of inconsistent requests and facilitates later protocol extensions (e.g., for authenticating or signing requests).</p><p>Another critical point is logging. The URLShortenerClient logs all relevant steps – from request creation to response processing. This transparency is crucial for understanding the exact process in the event of an error. Especially during the development phase and when integrating new features such as expiresAt, logging provides valuable insights into the timing, format and status of the data transfer.</p><p>A typical log snippet might look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">INFO</span><span class="w"/><span class="k">Create</span><span class="w"/><span class="n">custom</span><span class="w"/><span class="n">mapping</span><span class="w"/><span class="k">alias</span><span class="o">=</span><span class="s1">'test123'</span><span class="w"/><span class="n">url</span><span class="o">=</span><span class="s1">'https://example.com'</span><span class="w"/><span class="n">expiredAt</span><span class="o">=</span><span class="s1">'2025-12-31T23:59:00Z'</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">INFO</span><span class="w"/><span class="n">createCustomMapping</span><span class="w"/><span class="o">-</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">-</span><span class="w"/><span class="s1">'{"url": "https://example.com", "alias": "test123", "expiresAt": "2025-12-31T23:59:00Z"}'</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">INFO</span><span class="w"/><span class="n">createCustomMapping</span><span class="w"/><span class="o">-</span><span class="w"/><span class="n">jsonResponse</span><span class="w"/><span class="o">-</span><span class="w"/><span class="s1">'{"shortCode": "ex-9A7", "originalUrl": "https://example.com", "expiresAt": "2025-12-31T23:59:00Z"}'</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">INFO</span><span class="w"/><span class="n">shortUrlMapping</span><span class="p">..</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="p">[</span><span class="n">shortCode</span><span class="o">=</span><span class="n">ex</span><span class="o">-</span><span class="mi">9</span><span class="n">A7</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="o">=</span><span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">example</span><span class="p">.</span><span class="n">com</span><span class="p">,</span><span class="w"/><span class="n">expiresAt</span><span class="o">=</span><span class="mi">2025</span><span class="o">-</span><span class="mi">12</span><span class="o">-</span><span class="mi">31</span><span class="n">T23</span><span class="p">:</span><span class="mi">59</span><span class="p">:</span><span class="mi">00</span><span class="n">Z</span><span class="p">]</span></span></span></code></pre></div></div><p>This structured logging follows a clear pattern that makes all the steps involved traceable. It is intended not only for debugging, but also for integration into an audit or monitoring solution in the long term.</p><p>In conclusion, it should be noted that the client layer is now fully<strong>context-sensitive</strong>. It recognises optionally passed expiration dates, validates inputs, communicates clearly structured JSON payloads, and correctly interprets server-side responses. This creates a robust, well-defined interface between the user interface and the backend that can easily accommodate future extensions, such as custom policies or metadata.</p><h2 id="server-api-and-handler">Server API and Handler</h2><p>The server layer forms the backbone of the entire architecture. It receives requests, interprets the data structures, and coordinates the creation, storage, or deletion of short links. With the introduction of the expiration date (expiresAt), the API has been extended by a significant semantic dimension. The goal was to integrate this new information into the existing request-response flow without causing compatibility issues with older clients.</p><p>The central point of contact for incoming POST requests to create a short link is the ShortenHandler. This handles the JSON payload, performs validations, and interacts with the UrlMappingStore. In doing so, the processing has been extended to correctly extract the expiration date from the JSON object and pass it to persistence.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">final String body = readBody(ex.getRequestBody());</span></span><span class="line"><span class="cl">ShortenRequest req = fromJson(body, ShortenRequest.class);</span></span><span class="line"><span class="cl">if (isNullOrBlank(req.getUrl())) {</span></span><span class="line"><span class="cl">  writeJson(ex, BAD_REQUEST, "Missing 'url'");</span></span><span class="line"><span class="cl">  return;</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl">final Result<span class="nt">&lt;ShortUrlMapping&gt;</span> urlMappingResult =</span></span><span class="line"><span class="cl">    store.createMapping(req.getShortURL(), req.getUrl(), req.getExpiresAt());</span></span><span class="line"><span class="cl">urlMappingResult</span></span><span class="line"><span class="cl">  .ifPresentOrElse(success -&gt; logger().info("mapping created success {}", success.toString()),</span></span><span class="line"><span class="cl">                   failed -&gt; logger().info("mapping created failed - {}", failed));</span></span><span class="line"><span class="cl">urlMappingResult</span></span><span class="line"><span class="cl">  .ifSuccess(mapping -&gt; {</span></span><span class="line"><span class="cl">    final Headers h = ex.getResponseHeaders();</span></span><span class="line"><span class="cl">    h.add("Location", "/r/" + mapping.shortCode());</span></span><span class="line"><span class="cl">    writeJson(ex, fromCode(201), toJson(mapping));</span></span><span class="line"><span class="cl">  })</span></span><span class="line"><span class="cl">  .ifFailure(errorJson -&gt; {</span></span><span class="line"><span class="cl">    try {</span></span><span class="line"><span class="cl">      var parsed = JsonUtils.parseJson(errorJson);</span></span><span class="line"><span class="cl">      var errorCode = Integer.parseInt(parsed.get("code"));</span></span><span class="line"><span class="cl">      var message = parsed.get("message");</span></span><span class="line"><span class="cl">      writeJson(ex, fromCode(errorCode), message);</span></span><span class="line"><span class="cl">    } catch (Exception e) {</span></span><span class="line"><span class="cl">      writeJson(ex, CONFLICT, errorJson);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  });</span></span></code></pre></div></div><p>The ShortenHandler deliberately does not use a framework, but works with native Java APIs (HttpExchange, HttpURLConnection) to keep the control flow completely transparent. This decision is not only for traceability but also enables a precise understanding of how HTTP works at the lowest level. The code clearly shows the steps of the server cycle: read body, deserialise, validate, invoke domain logic, and write response.</p><p>Another example is the ListHandler, which has been slightly modified to take advantage of the modern Sequenced API (List.getFirst()), thus increasing readability:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private static String first(Map<span class="nt">&lt;String</span><span class="err">,</span><span class="err">List&lt;String</span><span class="nt">&gt;</span>&gt; q, String key) {</span></span><span class="line"><span class="cl">  var v = q.get(key);</span></span><span class="line"><span class="cl">  return (v == null || v.isEmpty()) ? null : v.getFirst();</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Special attention is paid to robust JSON data processing. Here, it has been ensured that line breaks and escape sequences do not cause parsing problems. In the JsonUtils module, a cleanup step was introduced before parsing:</p><p>s = s.replaceAll("\\n", &ldquo;&rdquo;);</p><p>This prevents multi-line JSON data from leading to errors – a typical stumbling block for APIs that process manually generated or logged payloads.</p><p>Overall, the server API deliberately remains<strong>flat and declarative</strong>. Each operation represents a clearly defined domain action; there is no excessive branching or hidden state change. By adding expiresAt, this style is retained, and the system continues to react deterministically: A request creates a mapping, optionally with an expiration date, and returns the complete record as JSON.</p><p>This simplicity is not a coincidence, but an expression of a design principle that runs through all levels of the application:<strong>explicit data flows instead of implicit magic</strong>. The result is a system that remains understandable for both users and developers and can be reliably expanded.</p><h2 id="persistence-and-store-implementations">Persistence and Store Implementations</h2><p>The persistence layer is the foundation on which the entire system&rsquo;s reliability rests. With the introduction of the expiration date (expiresAt), it had to be expanded accordingly so that this new information can be reliably stored, queried and evaluated – regardless of whether the data is stored in memory or in a persistent database such as EclipseStore.</p><p>At the center of this layer is the UrlMappingUpdater interface, which has been extended by a new method. This method adds the Instant expiredAt parameter to the previous signatures, so that the persistence layer can now explicitly handle expiration times.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public interface UrlMappingUpdater {</span></span><span class="line"><span class="cl">  Result<span class="nt">&lt;ShortUrlMapping&gt;</span> createMapping(String originalUrl);</span></span><span class="line"><span class="cl">  Result<span class="nt">&lt;ShortUrlMapping&gt;</span> createMapping(String alias, String url);</span></span><span class="line"><span class="cl">  Result<span class="nt">&lt;ShortUrlMapping&gt;</span> createMapping(String alias, String url, Instant expiredAt);</span></span><span class="line"><span class="cl">  boolean delete(String shortCode);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This clearly states that every implementation must process flow information. This adaptation follows the principle of<strong>contract-based design</strong> – the interface defines which capabilities the specific implementation must possess without prescribing their technical details.</p><h3 id="inmemory-implementation">InMemory Implementation</h3><p>The first customization was done in the InMemoryUrlMappingStore. This class is primarily used for tests and volatile runtime environments and stores all mappings in a ConcurrentHashMap. By extending the createMapping methods, expiration data is now correctly transferred to the MappingCreator.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Override</span></span><span class="line"><span class="cl">public Result<span class="nt">&lt;ShortUrlMapping&gt;</span> createMapping(String alias, String originalUrl, Instant expiredAt) {</span></span><span class="line"><span class="cl">  logger().info("alias: {} - originalUrl: {} - expiredAt: {} ", alias, originalUrl, expiredAt);</span></span><span class="line"><span class="cl">  return creator.create(alias, originalUrl, expiredAt);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>In the MappingCreator itself, the expiration time is integrated into the ShortUrlMapping and stored directly when created:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public Result<span class="nt">&lt;ShortUrlMapping&gt;</span> create(String alias, String url, Instant expiredAt) {</span></span><span class="line"><span class="cl">  logger().info("createMapping - alias='{}' / url='{}' / expiredAt='{}'", alias, url, expiredAt);</span></span><span class="line"><span class="cl">  final String shortCode;</span></span><span class="line"><span class="cl">  if (!isNullOrBlank(alias)) {</span></span><span class="line"><span class="cl">    if (repository.containsKey(alias)) {</span></span><span class="line"><span class="cl">      return Result.failure("Alias already exists");</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    shortCode = alias;</span></span><span class="line"><span class="cl">  } else {</span></span><span class="line"><span class="cl">    shortCode = generator.generate();</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  var mapping = new ShortUrlMapping(shortCode, url, Instant.now(clock), Optional.ofNullable(expiredAt));</span></span><span class="line"><span class="cl">  store.accept(mapping);</span></span><span class="line"><span class="cl">  return Result.success(mapping);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This pattern shows that expiration information, if any, is directly part of the ShortUrlMapping domain object. The code is deliberately simple: no additional state, no special treatment, but only an optional value.</p><h3 id="eclipsestore-implementation">EclipseStore Implementation</h3><p>The EclipseStoreUrlMappingStore has also been adapted for permanent storage. The same principle applies here, but with a focus on long-term persistence.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Override</span></span><span class="line"><span class="cl">public Result<span class="nt">&lt;ShortUrlMapping&gt;</span> createMapping(String alias, String originalUrl, Instant expiredAt) {</span></span><span class="line"><span class="cl">  logger().info("alias: {} - originalUrl: {} - expiredAt: {}", alias, originalUrl, expiredAt);</span></span><span class="line"><span class="cl">  return creator.create(alias, originalUrl, expiredAt);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Tightly coupling with the same MappingCreator ensures complete consistency in behaviour between the InMemory and EclipseStore variants. The only difference is the storage duration: While the data in the InMemory store is lost on restart, it remains persistent in the EclipseStore.</p><h3 id="uniformity-and-compatibility">Uniformity and Compatibility</h3><p>A central goal of these adaptations was the<strong>complete equal treatment of all persistent species</strong>. Whether it&rsquo;s testing, development mode, or production, all paths use the same objects and methods. This eliminates the risk of divergent logic across different storage types. Changes to the domain, such as the introduction of expiresAt, therefore only have to be implemented in one place.</p><h3 id="advantages-of-the-approach">Advantages of the approach</h3><p>This consistent standardization brings several advantages:</p><ol><li><strong>Transparency:</strong> Every mapping operation is traceable and documented in the log.</li><li><strong>Consistency:</strong> InMemory and EclipseStore stores behave identically.</li><li><strong>Extensibility:</strong> New storage mechanisms (e.g., SQL, key-value store, cloud) can be easily added as long as they fulfil the interface contract.</li></ol><p>With this extension, the persistence layer becomes the system&rsquo;s reliable backbone. The expiration date is now a full-fledged part of the data model – precisely recorded, securely stored and retrievable at any time. This means that future functions such as automatic cleaning of expired entries or time-based statistics can be implemented directly on this basis.</p><h2 id="domain-model-and-defaults">Domain Model and Defaults</h2><p>The class ShortenRequest has been extended to include the field expiresAt. This field allows you to pass an optional expiration time, which is set by the user in the UI and delivered to the server as an instant in JSON format. This information thus becomes a full-fledged component of the data model and can be further processed at both the transport layer and the persistence layer.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ShortenRequest</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">shortURL</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiresAt</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="nf">ShortenRequest</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">shortURL</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiresAt</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">url</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">url</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">shortURL</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">shortURL</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">expiresAt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">expiresAt</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="nf">getExpiresAt</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">expiresAt</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setExpiresAt</span><span class="p">(</span><span class="n">Instant</span><span class="w"/><span class="n">expiresAt</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">expiresAt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">expiresAt</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toJson</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">var</span><span class="w"/><span class="n">a</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">shortURL</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"\"null\""</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"\""</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">JsonUtils</span><span class="p">.</span><span class="na">escape</span><span class="p">(</span><span class="n">shortURL</span><span class="p">)</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"\""</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">var</span><span class="w"/><span class="n">b</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">expiresAt</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"\"null\""</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"\""</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">JsonUtils</span><span class="p">.</span><span class="na">escape</span><span class="p">(</span><span class="n">expiresAt</span><span class="p">.</span><span class="na">toString</span><span class="p">())</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"\""</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="s">"""</span></span></span><span class="line"><span class="cl"><span class="s">        {</span></span></span><span class="line"><span class="cl"><span class="s">          \"url\": \"%s\",</span></span></span><span class="line"><span class="cl"><span class="s">          \"alias\": %s,</span></span></span><span class="line"><span class="cl"><span class="s">          \"expiresAt\": %s</span></span></span><span class="line"><span class="cl"><span class="s">        }</span></span></span><span class="line"><span class="cl"><span class="s">        """</span><span class="p">.</span><span class="na">formatted</span><span class="p">(</span><span class="n">JsonUtils</span><span class="p">.</span><span class="na">escape</span><span class="p">(</span><span class="n">url</span><span class="p">),</span><span class="w"/><span class="n">a</span><span class="p">,</span><span class="w"/><span class="n">b</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>It is important to note that the expiresAt value is not mandatory. This keeps existing clients, and server calls compatible, even if they don&rsquo;t set the field. The domain model was deliberately designed to remain backwards-compatible and extensible, an essential principle when introducing new features.</p><h3 id="shorturlmapping-as-a-central-link">ShortUrlMapping as a central link</h3><p>The ShortUrlMapping class represents the central data element between the client and the server. It contains all the relevant information of a short link: the generated shortcode, the destination URL, the creation date and, optionally, the expiration date. By using Optional<Instant>, the possible absence of an expiration date is explicitly modelled.</p><p>public record ShortUrlMapping(String shortCode, String originalUrl, Instant createdAt, Optional<Instant> expiresAt) { }</p><p>This decision underscores the model&rsquo;s functional properties: an immutable data element that is fully defined only when it is created. Changes to a mapping are always made through new creations or explicit updates – never through silent mutations.</p><h3 id="defaultvalues--central-system-constants">DefaultValues – Central System Constants</h3><p>In addition to the model classes, the DefaultValues class has also been extended. It contains constants that are used throughout the application, especially the base URL for generated short links.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">DefaultValues</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">TODO</span><span class="w"/><span class="o">-</span><span class="w"/><span class="n">must</span><span class="w"/><span class="n">be</span><span class="w"/><span class="n">editable</span><span class="w"/><span class="n">by</span><span class="w"/><span class="n">user</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">SHORTCODE_BASE_URL</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"https://3g3.eu/"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">ADMIN_SERVER_PORT</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">9090</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">ADMIN_SERVER_HOST</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"localhost"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">ADMIN_SERVER_PROTOCOL</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"http"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">more</span><span class="w"/><span class="n">path</span><span class="w"/><span class="n">definitions</span><span class="w"/><span class="p">...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The constant SHORTCODE_BASE_URL serves as the basic component for generating and displaying short links in the user interface. Although it is currently statically defined, it has already been noted that it will be dynamically configurable in a future iteration. This lays the foundation for flexible deployment scenarios in which different environments (e.g., development, test, production) can use their own base URLs.</p><h3 id="aliaspolicy-with-logging">AliasPolicy with Logging</h3><p>Another component of the domain model is the AliasPolicy, which defines rules for valid aliases. Logging has been added as part of the enhancements to make the validation processes easier to understand:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">AliasPolicy</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Validation</span><span class="w"/><span class="nf">validate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">alias</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">HasLogger</span><span class="p">.</span><span class="na">staticLogger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"validate - {}"</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">alias</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">alias</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Validation</span><span class="p">.</span><span class="na">fail</span><span class="p">(</span><span class="n">Reason</span><span class="p">.</span><span class="na">NULL_OR_BLANK</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">alias</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">MIN</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Validation</span><span class="p">.</span><span class="na">fail</span><span class="p">(</span><span class="n">Reason</span><span class="p">.</span><span class="na">TOO_SHORT</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">alias</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">MAX</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Validation</span><span class="p">.</span><span class="na">fail</span><span class="p">(</span><span class="n">Reason</span><span class="p">.</span><span class="na">TOO_LONG</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="w"/><span class="n">ALLOWED_PATTERN</span><span class="p">.</span><span class="na">matcher</span><span class="p">(</span><span class="n">alias</span><span class="p">).</span><span class="na">matches</span><span class="p">())</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Validation</span><span class="p">.</span><span class="na">fail</span><span class="p">(</span><span class="n">Reason</span><span class="p">.</span><span class="na">INVALID_CHARS</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">Validation</span><span class="p">.</span><span class="na">success</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This logging makes faulty aliases immediately visible, making troubleshooting the interaction between the UI and the backend much easier.</p><p>With these adjustments, the domain model becomes a robust, clearly structured core of the application. The central entity ShortUrlMapping fully reflects the real-world state of a shortlink, while ShortenRequest controls the creation of new entries and provides system-wide constants to DefaultValues. All extensions remain consistent with the original design principle: simple, functional structures that precisely define what a user can create, modify, or retrieve.</p><h2 id="json-serialisation-and-deserialization">JSON serialisation and deserialization</h2><p>The reliability of communication between the components of an application depends crucially on the quality of the serialisation layer. This chapter describes how JSON processing has been extended and stabilised to safely transport the new expiresAt field while improving code readability and error tolerance.</p><h3 id="extending-serialisation-in-jsonutils">Extending Serialisation in JsonUtils</h3><p>The JsonUtils class forms the backbone of JSON processing in the project. It provides both generic helper methods and specific serialisation routines for the most critical domain objects. With the introduction of the expiration date, it was necessary to ensure this field was correctly integrated into JSON documents without affecting legacy data formats.</p><p>In the method for serialising ShortenRequest, the field expiresAt has therefore been added:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">dto</span><span class="w"/><span class="k">instanceof</span><span class="w"/><span class="n">ShortenRequest</span><span class="w"/><span class="n">req</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">Object</span><span class="o">&gt;</span><span class="w"/><span class="n">m</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">LinkedHashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">m</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="s">"url"</span><span class="p">,</span><span class="w"/><span class="n">req</span><span class="p">.</span><span class="na">getUrl</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">m</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="s">"alias"</span><span class="p">,</span><span class="w"/><span class="n">req</span><span class="p">.</span><span class="na">getShortURL</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">m</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="s">"expiresAt"</span><span class="p">,</span><span class="w"/><span class="n">req</span><span class="p">.</span><span class="na">getExpiresAt</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"/><span class="n">toJson</span><span class="p">(</span><span class="n">m</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This change will automatically include the expiration date, if it exists. If it is not set, the field appears as null in the JSON and thus remains syntactically valid. This explicit representation of null values improves readability and allows the server to clearly distinguish between &ldquo;not set&rdquo; and &ldquo;deliberately empty&rdquo;.</p><h3 id="extending-deserialization">Extending Deserialization</h3><p>Analogous to serialisation, deserialization has also been extended to read expiresAt from JSON data correctly. In the fromJson method, the customisation is done:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">type</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">ShortenRequest</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"/><span class="n">url</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"url"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"/><span class="n">alias</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"alias"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Instant</span><span class="w"/><span class="n">expiresAt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">parseInstantSafe</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"expiresAt"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"/><span class="p">(</span><span class="n">T</span><span class="p">)</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ShortenRequest</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">expiresAt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The parseInstantSafe function converts an ISO-8601 string to an instant object and handles invalid or empty values gracefully. This error resistance is significant for clients that may send different or older JSON structures.</p><h3 id="purge-of-incoming-json-data">Purge of incoming JSON data</h3><p>A common problem with APIs is reading in JSON data that contains unintentional line breaks or extra escape characters. To prevent parsing errors, simple preprocessing has been introduced in JsonUtils.parseJson:</p><p>s = s.replaceAll("\\n", &ldquo;&rdquo;);</p><p>This step removes all line breaks before the parser runs. This reliably recognises and correctly interprets both manually formatted JSON files and logged messages. This customisation makes the system more robust against inconsistent formatting that is common in real-world environments. (At this point, however, I am aware that it is far from sufficient&hellip;)</p><h3 id="optimisation-of-json-output">Optimisation of JSON output</h3><p>As part of these changes, the toJsonListing method has also been revised. Instead of a complicated StringBuilder structure, a simple, readable string concatenation is now used:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">return</span><span class="w"/><span class="s">"{"</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="s">"\"mode\":\""</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">escape</span><span class="p">(</span><span class="n">mode</span><span class="p">)</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"\","</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="s">"\"count\":"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">count</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">","</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="s">"\"items\":"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">toJsonArrayOfObjects</span><span class="p">(</span><span class="n">items</span><span class="p">)</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="s">"}"</span><span class="p">;</span></span></span></code></pre></div></div><p>This simplification reduces the susceptibility to errors and makes debugging easier. Especially in systems that do not require frameworks, code readability is a decisive factor for maintainability and error diagnosis.</p><h3 id="consistency-and-interoperability">Consistency and interoperability</h3><p>An essential aspect of the revision of serialisation was maintaining<strong>interoperability</strong> across different clients and API versions. Since all fields are still serialised on a string-by-string basis and the JSON structure is explicit, the data exchange remains fully compatible even with older clients. This means that a client that does not send expiresAt will be accepted by the server, and a server that does not expect the field will ignore it.</p><p>This loose coupling between transmitter and receiver is a central design goal of the project. It allows incremental expansions without updating all components at once.</p><p>The revision to JSON processing strengthens the application&rsquo;s robustness and future-proofing. By specifically extending JsonUtils to include the expiresAt field, cleaning incoming data, and simplifying the output, serialisation is now both technically stable and semantically precise. It thus meets the requirements of a modern, evolvable interface that remains clearly comprehensible for both automated processes and human readers.</p><h2 id="security-and-robustness-in-the-ui-flow">Security and robustness in the UI flow</h2><p>As the complexity of the user interface grows, so does the responsibility to ensure that all interactions remain predictable, valid, and stable. This chapter explains how security considerations and robustness were built directly into the UI flow – from input validation to defensive navigation decisions to dealing with external browser APIs.</p><h3 id="input-validations-in-the-generation-dialogue">Input validations in the generation dialogue</h3><p>The most important security aspect in the UI is validating user input. As soon as a new short link is created, the application checks whether the entered URL contains a valid scheme. This prevents potential attacks by manipulated or unsupported protocols at an early stage.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">binder</span><span class="p">.</span><span class="na">forField</span><span class="p">(</span><span class="n">urlField</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">asRequired</span><span class="p">(</span><span class="s">"URL must not be empty"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">withValidator</span><span class="p">(</span><span class="n">url</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">url</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="s">"http://"</span><span class="p">)</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">url</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="s">"https://"</span><span class="p">),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                  </span><span class="s">"Only HTTP(S) URLs allowed"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">bind</span><span class="p">(</span><span class="n">ShortenRequest</span><span class="p">::</span><span class="n">getUrl</span><span class="p">,</span><span class="w"/><span class="n">ShortenRequest</span><span class="p">::</span><span class="n">setUrl</span><span class="p">);</span></span></span></code></pre></div></div><p>This simple validation only accepts URLs that can be reached via secure or well-defined transport protocols. This protects both the user and the application from unwanted interactions with unsafe targets. However, it is not yet full input validation.</p><p>Another part of the validation concerns the expiration date. Here, it is ensured that a selected point in time is always in the future:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">exp</span><span class="p">.</span><span class="na">isPresent</span><span class="p">()</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">exp</span><span class="p">.</span><span class="na">get</span><span class="p">().</span><span class="na">isBefore</span><span class="p">(</span><span class="n">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Expiry must be in the future"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This mechanism protects against incorrect entries and inconsistent data states, especially when users edit the form multiple times or select times that have already expired.</p><h3 id="defensive-navigation-in-the-detail-dialogue">Defensive navigation in the detail dialogue</h3><p>The detail dialogue (DetailsDialog) also follows the principle of secure interaction. If a saved URL is opened via the &ldquo;Open&rdquo; button, this is done exclusively by calling the method UI.getCurrent().getPage().open(), but only if the destination is clearly recognised as an HTTP or HTTPS link. This prevents internal or local resources from being accidentally or intentionally called via the UI.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">openBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">originalUrl</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="s">"http://"</span><span class="p">)</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">originalUrl</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="s">"https://"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">fireEvent</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">OpenEvent</span><span class="p">(</span><span class="k">this</span><span class="p">,</span><span class="w"/><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">originalUrl</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">getUI</span><span class="p">().</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">ui</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">getPage</span><span class="p">().</span><span class="na">open</span><span class="p">(</span><span class="n">originalUrl</span><span class="p">,</span><span class="w"/><span class="s">"_blank"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Invalid URL scheme"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>This decision strengthens the separation between internal and external resources. User actions are controlled to prevent unwanted side effects outside the application.</p><h3 id="dealing-with-the-clipboard-api">Dealing with the Clipboard API</h3><p>Another security-relevant topic is how to use the native<strong>Clipboard API</strong>. For data protection reasons, this is only available in secure browser contexts, i.e. via HTTPS or localhost. The application uses this API to copy shortcodes and URLs to the clipboard conveniently. If access is not allowed in the current context, the call does not result in an error, but is silently discarded – an example of<strong>defensive programming behaviour</strong> in the UI.</p><p>UI.getCurrent().getPage().executeJs(&ldquo;navigator.clipboard.writeText($0)&rdquo;, SHORTCODE_BASE_URL + m.shortCode());</p><p>This non-blocking call avoids JavaScript errors and keeps the UI stable even if the browser rejects the action. The application always responds in a controlled manner and remains in a valid state.</p><h3 id="consistent-feedback-and-fault-tolerance">Consistent feedback and fault tolerance</h3><p>A core element of robustness is the<strong>feedback system</strong>. Every user command – whether successful or incorrect – triggers visual feedback. The application consistently uses the Vaadin notification component for this purpose. It provides information about validation errors, successful copying operations and system messages without interrupting the workflow. This asynchronous reporting system supports the idea that a user can continue at any time, even if a single operation fails.</p><p>Notification.show(&ldquo;Alias already assigned or error saving&rdquo;, 3000, Notification.Position.MIDDLE);</p><p>This type of error communication avoids frustration and contributes to perceived stability. The user remains informed, but never blocked.</p><p>The measures described in this chapter – input validations, defensive navigation, and safe clipboard use – share a common goal:<strong>robustness through caution</strong>. Every action in the user interface is checked, every external interface is secured, and every user interaction is clearly reported. This allows the UI to achieve a high level of error tolerance without sacrificing ease of use. This balance of security and user-friendliness concludes the technical implementation of the detailed dialogue. It lays the foundation for the upcoming enhancements, in which security and user experience will continue to go hand in hand.</p><h2 id="result">Result</h2><p>With today&rsquo;s Advent calendar, the application has reached a decisive degree of maturity. While the first parts mainly laid the structural and functional foundations, today it is all about the depth of detail and the quality of interaction. The focus was on transitioning from a technically functional interface to a well-thought-out, user-centric application.</p><p>The newly introduced<strong>detail dialogue</strong> marks a central turning point: it allows viewing individual entries in context without leaving the overview. This concept combines technical clarity with ease of use, creating a modular structure that can easily accommodate future expansions. By using events to decouple the components, the architecture remains clean, traceable and testable.</p><p>The integration of the expiration date<strong>is also</strong> proving to be a milestone in the system&rsquo;s functional development. From input in the UI to the client layer to persistence and JSON processing, the new parameter has been consistently integrated into all layers. The original design was deliberately kept simple – each layer knows only its own responsibility, and none contains logic that anticipates another layer. The result is a consistent, precise data flow that combines technical precision with semantic transparency.</p><p>Another critical advance is improving<strong>the user experience (UX).</strong> Consistent interaction patterns, precise feedback, and security-conscious behaviour make the application both intuitive and trustworthy. The combination of immediate feedback, non-blocking error handling, and a harmonious visual-functional design shows how technological rigour and user focus can go hand in hand.</p><p>From an architectural point of view, this part of the Advent calendar illustrates that even in small projects**, cleanliness, coherence, and expandability** are the key success factors. The clear separation between UI, client, server, and persistence not only enables efficient maintenance but also opens the way for future modules – such as administrative views, bulk operations, or security policies for user groups.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/DetailDialog-Part01-SteamPunk.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/DetailDialog-Part01-SteamPunk.png"/><enclosure url="https://svenruppert.com/images/2025/12/DetailDialog-Part01-SteamPunk.png" type="image/jpeg" length="0"/></item><item><title>Advent Calendar - 2025 - Detail Dialog - Part 1</title><link>https://svenruppert.com/posts/advent-calendar-2025-detail-dialog-part-1/</link><pubDate>Sat, 06 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-detail-dialog-part-1/</guid><description>Classification and objectives from a UI perspective Today&rsquo;s Advent Calendar Day focuses specifically on the interaction level prepared in the previous parts. While the basic structure of the user interface and the layout were defined at the beginning, and the interactive table view with sorting, filtering and dynamic actions was subsequently established, it is now a matter of making the transition from overview to detailed observation consistent. The user should no longer only see a tabular collection of data points, but should receive a view tailored to the respective object that enables contextual actions.</description><content:encoded>&lt;![CDATA[<h2 id="classification-and-objectives-from-a-ui-perspective">Classification and objectives from a UI perspective</h2><p>Today&rsquo;s Advent Calendar Day focuses specifically on the interaction level prepared in the previous parts. While the basic structure of the user interface and the layout were defined at the beginning, and the interactive table view with sorting, filtering and dynamic actions was subsequently established, it is now a matter of making the transition from overview to detailed observation consistent. The user should no longer only see a tabular collection of data points, but should receive a view tailored to the respective object that enables contextual actions.</p><ol><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-1/#classification-and-objectives-from-a-ui-perspective">Classification and objectives from a UI perspective</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-1/#overviewview-interactive-enhancements">OverviewView: Interactive enhancements</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-1/#detail-view-as-a-standalone-ui-component-detailsdialog">Detail view as a standalone UI component (DetailsDialog)</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-1/#createview-expiration-date-in-the-ui">CreateView: Expiration date in the UI</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-1/#navigation-and-package-structure">Navigation and package structure</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-1/#interaction-patterns-and-ux-coherence">Interaction patterns and UX coherence</a><ol><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-1/#uniform-copying-behaviour">Uniform copying behaviour</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-1/#context-menus-and-multiple-interactions">Context menus and multiple interactions</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-1/#consistency-of-feedback">Consistency of feedback</a></li><li><a href="https://svenruppert.com/2025/12/06/advent-calendar-2025-detail-dialog-part-1/#https-requirement-for-clipboard">HTTPS Requirement for Clipboard</a></li></ol></li></ol><p><strong>The source code for this version can be found on GitHub at</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-03">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-03</a></p><p>Here&rsquo;s the screenshot of the version we&rsquo;re implementing now.</p><p><figure><img src="/images/2025/12/image-9-1024x391.png" alt="Screenshot of the URL Shortener’s Overview page displaying a table of shortcodes, URLs, creation dates, expiration indicators, and action buttons. Contains a search bar and filters for sorting and displaying results." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-12-1024x712.png" alt="Screenshot of the URL Shortener application overview, displaying a table with shortcodes and their corresponding original URLs. A detailed dialog is open showing details for the shortcode ‘sru’ with options to open, copy, and delete the entry." loading="lazy" decoding="async"/><figure><img src="/images/2025/12/image-11-1024x419.png" alt="Screenshot of the URL Shortener Overview interface, displaying an interactive table with shortcodes, original URLs, creation dates, expiration statuses, and action buttons for details, opening URLs, copying shortcodes, and deleting entries." loading="lazy" decoding="async"/></p><p>The central goal of this chapter is to improve the user experience without sacrificing the simplicity of what has been done so far. The interface is extended with a<strong>detailed view</strong> implemented as a<strong>standalone dialogue</strong> , thereby deliberately creating a clear framework between the overall list and the individual object. This decision follows the principle of the cognitive separation of information spaces: an overview serves to orient and find relevant entries. At the same time, the detailed view creates an isolated, focused working environment.</p><p>From an architectural point of view, this extension is an essential step towards a<strong>component-oriented UI</strong> in which each functional unit – such as creating, displaying or deleting short links – is represented by its own, clearly defined components. The new dialogue includes all the necessary UI elements, validations, and event flows for displaying and interacting with a single ShortUrlMapping object. This decoupling not only enables better testability and reusability but also reduces the cognitive overhead in the primary grid, which previously had to record all interactions directly.</p><p>The dialogue serves as an<strong>interactive interface</strong> between the visual representation and the underlying data model. It reflects the current object, provides copy and navigation actions, and provides visual feedback on the entry&rsquo;s status – for example, via a colour-coded expiration date. This modular structure allows the user to check details, perform actions, or remove erroneous entries without losing the application&rsquo;s context.</p><h2 id="overviewview-interactive-enhancements">OverviewView: Interactive enhancements</h2><p>With the third expansion stage of the user interface, the previous list view is not only expanded, but also functionally upgraded. The<strong>OverviewView</strong> forms the foundation of the daily work with the created short links and is supplemented in this step by several mechanisms that make the behaviour of the application more natural and efficient.</p><p>A central aspect concerns<strong>the reactivity of the search fields</strong>. In previous versions, search queries were triggered immediately after each input, which was technically correct but inefficient in practice, using the ValueChangeMode.LAZY, in combination with a 400-millisecond delay, the system does not react until the user has completed their input. This delay serves as a natural &ldquo;pause for thought&rdquo; and prevents unnecessary updates to the grid. In addition, the<strong>enter-key flow has been</strong> activated so that a targeted search confirmation can be performed via the keyboard – a typical usage pattern in the professional environment.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="na">setValueChangeMode</span><span class="p">(</span><span class="n">ValueChangeMode</span><span class="p">.</span><span class="na">LAZY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="na">setValueChangeTimeout</span><span class="p">(</span><span class="n">400</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">refresh</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="na">setValueChangeMode</span><span class="p">(</span><span class="n">ValueChangeMode</span><span class="p">.</span><span class="na">LAZY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="na">setValueChangeTimeout</span><span class="p">(</span><span class="n">400</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">refresh</span><span class="p">());</span></span></span></code></pre></div></div><p>A second improvement concerns the<strong>variety of interactions</strong> in the grid. Instead of acting exclusively via buttons, the user can now open a data record by<strong>double-clicking</strong> or pressing<strong>Enter</strong>. This form of interaction is not only faster, but also meets the expectations you are used to from desktop applications. To further simplify operation, a<strong>context menu</strong> has been added that automatically offers the appropriate actions when right-clicking: View details, open target URL, copy shortcode, or delete the entry.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">grid.addItemDoubleClickListener(ev -&gt; openDetailsDialog(ev.getItem()));</span></span><span class="line"><span class="cl">grid.addItemClickListener(ev -&gt; {</span></span><span class="line"><span class="cl"> if (ev.getClickCount() == 2) openDetailsDialog(ev.getItem());</span></span><span class="line"><span class="cl">});</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">GridContextMenu<span class="nt">&lt;ShortUrlMapping&gt;</span> menu = new GridContextMenu<span class="err">&lt;</span>&gt;(grid);</span></span><span class="line"><span class="cl">menu.addItem("Show details", e -&gt; e.getItem().ifPresent(this::openDetailsDialog));</span></span><span class="line"><span class="cl">menu.addItem("Open URL", e -&gt; e.getItem().ifPresent(m -&gt; UI.getCurrent().getPage().open(m.originalUrl(), "_blank")));</span></span><span class="line"><span class="cl">menu.addItem("Copy shortcode", e -&gt; e.getItem().ifPresent(m -&gt; UI.getCurrent().getPage().executeJs("navigator.clipboard.writeText($0)", m.shortCode())));</span></span><span class="line"><span class="cl">menu.addItem("Delete...", e -&gt; e.getItem().ifPresent(m -&gt; confirmDelete(m.shortCode())));</span></span></code></pre></div></div><p>In addition to functionality, the grid column layout has been revised. The shortcode is now displayed in a<strong>monospace font</strong> , which makes it easier to quickly grasp and compare visually. In addition, a small copy symbol allows copying the entire short link to the clipboard with a mouse click. JavaScript is used to call up the browser&rsquo;s native clipboard service.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="na">addComponentColumn</span><span class="p">(</span><span class="n">m</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">code</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">code</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"font-family"</span><span class="p">,</span><span class="w"/><span class="s">"ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">copy</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Icon</span><span class="p">(</span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">COPY</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">copy</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_TERTIARY_INLINE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">copy</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="s">"Copy ShortUrl"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">copy</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">getPage</span><span class="p">().</span><span class="na">executeJs</span><span class="p">(</span><span class="s">"navigator.clipboard.writeText($0)"</span><span class="p">,</span><span class="w"/><span class="n">SHORTCODE_BASE_URL</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Shortcode copied"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">wrap</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">code</span><span class="p">,</span><span class="w"/><span class="n">copy</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">wrap</span><span class="p">.</span><span class="na">setSpacing</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">wrap</span><span class="p">.</span><span class="na">setPadding</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">wrap</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}).</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Shortcode"</span><span class="p">).</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">setFrozen</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">setResizable</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">setFlexGrow</span><span class="p">(</span><span class="n">0</span><span class="p">);</span></span></span></code></pre></div></div><p>In the column with the original URL, the layout has been changed to an<strong>elliptical display</strong> : Long URLs are truncated, but remain fully visible via the tooltip. This detail improves readability without losing information.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="na">addComponentColumn</span><span class="p">(</span><span class="n">m</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">a</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Anchor</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">originalUrl</span><span class="p">(),</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">originalUrl</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">a</span><span class="p">.</span><span class="na">setTarget</span><span class="p">(</span><span class="s">"_blank"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">a</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"white-space"</span><span class="p">,</span><span class="w"/><span class="s">"nowrap"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"overflow"</span><span class="p">,</span><span class="w"/><span class="s">"hidden"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"text-overflow"</span><span class="p">,</span><span class="w"/><span class="s">"ellipsis"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"display"</span><span class="p">,</span><span class="w"/><span class="s">"inline-block"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"max-width"</span><span class="p">,</span><span class="w"/><span class="s">"100%"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">a</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setProperty</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">originalUrl</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">a</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}).</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"URL"</span><span class="p">).</span><span class="na">setFlexGrow</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="na">setResizable</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span></span></span></code></pre></div></div><p>Particularly noteworthy is the new<strong>Expires column</strong> , which<strong>colour-coded status indicators have visually supplemented</strong>. These are based on Lumo badges and show the remaining time until a link expires: green for active, yellow for expiring soon, and red for expired.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">grid</span><span class="p">.</span><span class="na">addComponentColumn</span><span class="p">(</span><span class="n">m</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">pill</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">expiresAt</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">ts</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">days</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Duration</span><span class="p">.</span><span class="na">between</span><span class="p">(</span><span class="n">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">(),</span><span class="w"/><span class="n">ts</span><span class="p">).</span><span class="na">toDays</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">days</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Expired"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">days</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Today"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"in "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">days</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" days"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}).</span><span class="na">orElse</span><span class="p">(</span><span class="s">"No expiry"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pill</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">getThemeList</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"badge pill small"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">expiresAt</span><span class="p">().</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">ts</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">d</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Duration</span><span class="p">.</span><span class="na">between</span><span class="p">(</span><span class="n">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">(),</span><span class="w"/><span class="n">ts</span><span class="p">).</span><span class="na">toDays</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">d</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="n">pill</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">getThemeList</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"error"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">else</span><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">d</span><span class="w"/><span class="o">&lt;=</span><span class="w"/><span class="n">3</span><span class="p">)</span><span class="w"/><span class="n">pill</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">getThemeList</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"warning"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">else</span><span class="w"/><span class="n">pill</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">getThemeList</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"success"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">pill</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}).</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Expires"</span><span class="p">).</span><span class="na">setAutoWidth</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">setResizable</span><span class="p">(</span><span class="kc">true</span><span class="p">).</span><span class="na">setFlexGrow</span><span class="p">(</span><span class="n">0</span><span class="p">);</span></span></span></code></pre></div></div><p>Finally, the display&rsquo;s<strong>ergonomics</strong> have been improved. The grid now works with reduced vertical spacing (compact mode) while retaining its readability thanks to alternating line colours. The line height has been adjusted so that as many entries as possible remain visible even on smaller monitors without making the UI look overloaded.</p><p>With these changes, the OverviewView is transformed from a purely management view into a central control tool that provides both a quick overview and deep interaction. This lays the foundation for integrating the detail dialogue – it complements this view with an object-related perspective, while the OverviewView continues to serve as the starting point for navigation.</p><h2 id="detail-view-as-a-standalone-ui-component-detailsdialog">Detail view as a standalone UI component (DetailsDialog)</h2><p>Now that the overview list has been expanded with interactive functions, the next logical step is to introduce a standalone UI component focused on the display and management of individual datasets. The goal is to create a modular and reusable view that operates independently of the main view but communicates with it via clearly defined events.</p><p>The dialogue is based on Vaadin&rsquo;s Dialogue class and opens for each selected ShortUrlMapping object. In doing so, it reads out all relevant properties of the passed object – shortcode, target URL, creation time and, optionally, the expiration date. These values are presented in a clearly structured format, supplemented by action buttons to open, copy and delete the entry.</p><p>The following excerpt shows the basic structure of the dialogue:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class DetailsDialog extends Dialog implements HasLogger {</span></span><span class="line"><span class="cl"> public static final ZoneId ZONE = ZoneId.systemDefault();</span></span><span class="line"><span class="cl"> private static final DateTimeFormatter DATE_TIME_FMT =</span></span><span class="line"><span class="cl"> DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm").withZone(ZONE);</span></span><span class="line"><span class="cl"> private final String shortCode;</span></span><span class="line"><span class="cl"> private final String originalUrl;</span></span><span class="line"><span class="cl"> private final Instant createdAt;</span></span><span class="line"><span class="cl"> private final Optional<span class="nt">&lt;Instant&gt;</span> expiresAt;</span></span><span class="line"><span class="cl"> private final TextField tfShort = new TextField("Shortcode");</span></span><span class="line"><span class="cl"> private final TextField tfUrl = new TextField("Original URL");</span></span><span class="line"><span class="cl"> private final TextField tfCreated = new TextField("Created on");</span></span><span class="line"><span class="cl"> private final TextField tfExpires = new TextField("Expires");</span></span><span class="line"><span class="cl"> private final Span statusPill = new Span();</span></span><span class="line"><span class="cl"> private final Button openBtn = new Button("Open", new Icon(VaadinIcon.EXTERNAL_LINK));</span></span><span class="line"><span class="cl"> private final Button copyShortBtn = new Button("Copy ShortURL", new Icon(VaadinIcon.COPY));</span></span><span class="line"><span class="cl"> private final Button copyUrlBtn = new Button("Copy URL", new Icon(VaadinIcon.COPY));</span></span><span class="line"><span class="cl"> private final Button deleteBtn = new Button("Delete...", new Icon(VaadinIcon.TRASH));</span></span><span class="line"><span class="cl"> private final Button closeBtn = new Button("Close");</span></span><span class="line"><span class="cl"> public DetailsDialog(ShortUrlMapping mapping) {</span></span><span class="line"><span class="cl"> Objects.requireNonNull(mapping, "mapping");</span></span><span class="line"><span class="cl"> this.shortCode = mapping.shortCode();</span></span><span class="line"><span class="cl"> this.originalUrl = mapping.originalUrl();</span></span><span class="line"><span class="cl"> this.createdAt = mapping.createdAt();</span></span><span class="line"><span class="cl"> this.expiresAt = mapping.expiresAt();</span></span><span class="line"><span class="cl"> setHeaderTitle("Details: " + shortCode);</span></span><span class="line"><span class="cl"> setModal(true);</span></span><span class="line"><span class="cl"> setDraggable(true);</span></span><span class="line"><span class="cl"> setResizable(true);</span></span><span class="line"><span class="cl"> setWidth("720px");</span></span><span class="line"><span class="cl"> openBtn.addThemeVariants(ButtonVariant.LUMO_PRIMARY);</span></span><span class="line"><span class="cl"> deleteBtn.addThemeVariants(ButtonVariant.LUMO_ERROR, ButtonVariant.LUMO_TERTIARY);</span></span><span class="line"><span class="cl"> var headerActions = new HorizontalLayout(openBtn, copyShortBtn, copyUrlBtn, deleteBtn);</span></span><span class="line"><span class="cl"> getHeader().add(headerActions);</span></span><span class="line"><span class="cl"> configureFields();</span></span><span class="line"><span class="cl"> var form = new FormLayout();</span></span><span class="line"><span class="cl"> form.add(tfShort, tfUrl, tfCreated, tfExpires, statusPill);</span></span><span class="line"><span class="cl"> form.setColspan(tfUrl, 2);</span></span><span class="line"><span class="cl"> add(form);</span></span><span class="line"><span class="cl"> closeBtn.addClickListener(e -&gt; close());</span></span><span class="line"><span class="cl"> getFooter().add(closeBtn);</span></span><span class="line"><span class="cl"> wireActions();</span></span><span class="line"><span class="cl"> }</span></span></code></pre></div></div><p>It is already clear here that the dialogue has a high degree of independence. It initialises its contents directly from the passed data object and uses a<strong>FormLayout</strong> to structure the fields. The input fields are<strong>read-only by default</strong> because the dialogue is used primarily for display.</p><p>The visual feedback on the expiration status is provided by a so-called<strong>status pill</strong> , which indicates in colour and text whether a short link is still active, about to expire or has already expired. This is done via a small helper method that provides a status description including a colour scheme:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">status</span><span class="w"/><span class="nf">computeStatusText</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">expiresAt</span><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">ts</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">d</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Duration</span><span class="p">.</span><span class="na">between</span><span class="p">(</span><span class="n">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">(),</span><span class="w"/><span class="n">ts</span><span class="p">).</span><span class="na">toDays</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">d</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Status</span><span class="p">(</span><span class="s">"Expired"</span><span class="p">,</span><span class="w"/><span class="s">"error"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">d</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Status</span><span class="p">(</span><span class="s">"Expires today"</span><span class="p">,</span><span class="w"/><span class="s">"warning"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">d</span><span class="w"/><span class="o">&lt;=</span><span class="w"/><span class="n">3</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Status</span><span class="p">(</span><span class="s">"Expires in "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">d</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" days"</span><span class="p">,</span><span class="w"/><span class="s">"warning"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Status</span><span class="p">(</span><span class="s">"Valid("</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">d</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" days left)"</span><span class="p">,</span><span class="w"/><span class="s">"success"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}).</span><span class="na">orElse</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Status</span><span class="p">(</span><span class="s">"No expiry"</span><span class="p">,</span><span class="w"/><span class="s">"contrast"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This status calculation complements the visual feedback from the summary table and provides a consistent representation across the entire application.</p><p>The real added value of the DetailsDialog lies in its<strong>event-orientation</strong>. Instead of the calling view (OverviewView) controlling all actions itself, the actions are defined as<strong>Vaadin events</strong>. This allows the dialogue to send signals such as<em>OpenEvent</em> ,<em>CopyShortcodeEvent</em> ,<em>CopyUrlEvent</em> or<em>DeleteEvent</em> to its environment without knowing what they mean:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public static class DeleteEvent extends ComponentEvent<span class="nt">&lt;DetailsDialog&gt;</span> {</span></span><span class="line"><span class="cl"> public final String shortCode;</span></span><span class="line"><span class="cl"> public DeleteEvent(DetailsDialog src, String sc) {</span></span><span class="line"><span class="cl"> super(src);</span></span><span class="line"><span class="cl"> this.shortCode = sc;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>In the OverviewView , these events are received and processed:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">var</span><span class="w"/><span class="n">dlg</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DetailsDialog</span><span class="p">(</span><span class="n">item</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dlg</span><span class="p">.</span><span class="na">addDeleteListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">confirmDelete</span><span class="p">(</span><span class="n">ev</span><span class="p">.</span><span class="na">shortCode</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dlg</span><span class="p">.</span><span class="na">addOpenListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Open URL {}"</span><span class="p">,</span><span class="w"/><span class="n">ev</span><span class="p">.</span><span class="na">originalUrl</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dlg</span><span class="p">.</span><span class="na">addCopyShortListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Copied shortcode {}"</span><span class="p">,</span><span class="w"/><span class="n">ev</span><span class="p">.</span><span class="na">shortCode</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dlg</span><span class="p">.</span><span class="na">addCopyUrlListener</span><span class="p">(</span><span class="n">ev</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Copied URL {}"</span><span class="p">,</span><span class="w"/><span class="n">ev</span><span class="p">.</span><span class="na">url</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dlg</span><span class="p">.</span><span class="na">open</span><span class="p">();</span></span></span></code></pre></div></div><p>This creates an apparent decoupling between the presentation and application logic. Dialogue handles representation and interaction; the surrounding view determines how to respond to events.</p><p>In summary, the<strong>DetailsDialog</strong> establishes an architectural principle that goes beyond the specific use case. The combination of a modular UI component, declarative events and a clearly defined data model not only makes the application more flexible, but also more maintainable in the long term. The user benefits from a coherent, clearly structured, detailed view, which makes working with individual entries much more convenient and transparent.</p><h2 id="createview-expiration-date-in-the-ui">CreateView: Expiration date in the UI</h2><p>With this step, the creation of new short links receives an important semantic addition: the optional<strong>expiration date</strong>. The aim is to precisely define the intended service life at the time of creation and to transport this information end-to-end – from the UI to the client to the server and into the persistence.</p><figure><img src="/images/2025/12/image-10-1024x287.png" alt="Screenshot of a web application interface titled ‘Create new short link’, featuring input fields for the target URL, an optional alias, expiration date and time, along with a ‘Shorten’ button." loading="lazy" decoding="async"/><p>From a UI point of view, the existing CreateView is extended with DatePicker and TimePicker, flanked by a checkbox labelled &ldquo;No expiry&rdquo;. This combination allows both the explicit determination of an end date and the conscious decision to set an unlimited validity. A small but crucial UX rule: the time will remain disabled until a date is selected, and both fields will be disabled if &ldquo;No expiry&rdquo; is enabled.</p><p>Fields and Basic Configuration</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private final TextField urlField = new TextField("Target URL");</span></span><span class="line"><span class="cl">private final TextField aliasField = new TextField("Alias (optional)");</span></span><span class="line"><span class="cl">private final Button shortenButton = new Button("Shorten");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">private final DatePicker expiresDate = new DatePicker("Expires (date)");</span></span><span class="line"><span class="cl">private final TimePicker expiresTime = new TimePicker("Expires (time)");</span></span><span class="line"><span class="cl">private final Checkbox noExpiry = new Checkbox("No expiry");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">private final FormLayout form = new FormLayout();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public CreateView() {</span></span><span class="line"><span class="cl"> setSpacing(true);</span></span><span class="line"><span class="cl"> setPadding(true);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> urlField.setWidthFull();</span></span><span class="line"><span class="cl"> aliasField.setWidth("300px");</span></span><span class="line"><span class="cl"> shortenButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> form.add(urlField, aliasField);</span></span><span class="line"><span class="cl"> configureExpiryFields();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> form.setResponsiveSteps(</span></span><span class="line"><span class="cl"> new FormLayout.ResponsiveStep("0", 1),</span></span><span class="line"><span class="cl"> new FormLayout.ResponsiveStep("600px", 2)</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl"> form.setColspan(urlField, 2);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var actions = new HorizontalLayout(shortenButton);</span></span><span class="line"><span class="cl"> actions.setAlignItems(Alignment.END);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Binder for validations</span></span><span class="line"><span class="cl"> Binder<span class="nt">&lt;ShortenRequest&gt;</span> binder = new Binder<span class="err">&lt;</span>&gt;(ShortenRequest.class);</span></span><span class="line"><span class="cl"> ShortenRequest request = new ShortenRequest();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> binder.forField(urlField)</span></span><span class="line"><span class="cl"> .asRequired("URL must not be empty")</span></span><span class="line"><span class="cl"> .withValidator(url -&gt; url.startsWith("http://") || url.startsWith("https://"), "Only HTTP(S) URLs allowed")</span></span><span class="line"><span class="cl"> .bind(ShortenRequest::getUrl, ShortenRequest::setUrl);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> binder.forField(aliasField)</span></span><span class="line"><span class="cl"> .withValidator(a -&gt; a == null || a.isBlank() || a.length()<span class="err">&lt;</span>= AliasPolicy.MAX, "Alias is too long (max " + AliasPolicy.MAX + ")")</span></span><span class="line"><span class="cl"> .withValidator(a -&gt; a == null || a.isBlank() || a.matches(REGEX_ALLOWED), "Only [A-Za-z0-9_-] allowed")</span></span><span class="line"><span class="cl"> .bind(ShortenRequest::getShortURL, ShortenRequest::setShortURL);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> shortenButton.addClickListener(_ -&gt; {</span></span><span class="line"><span class="cl"> var validated = binder.validate();</span></span><span class="line"><span class="cl"> if (validated.hasErrors()) return;</span></span><span class="line"><span class="cl"> if (!validateExpiryInFuture()) return;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> if (binder.writeBeanIfValid(request)) {</span></span><span class="line"><span class="cl"> computeExpiresAt().ifPresent(request::setExpiresAt);</span></span><span class="line"><span class="cl"> var code = createShortCode(request, computeExpiresAt());</span></span><span class="line"><span class="cl"> code.ifPresentOrElse(c -&gt; {</span></span><span class="line"><span class="cl"> Notification.show("Short link created: " + c);</span></span><span class="line"><span class="cl"> clearForm(binder);</span></span><span class="line"><span class="cl"> },</span></span><span class="line"><span class="cl"> () -&gt; Notification.show("Alias already assigned or error saving", 3000, Notification.Position.MIDDLE));</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> add(new H2("Create new short link"), form, actions);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Encapsulating process logic in small auxiliary methods improves readability and testability. The calculation uses the local time zone and provides an instant that can be processed unchanged on the server side.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private static final ZoneId ZONE = ZoneId.systemDefault();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">private void configureExpiryFields() {</span></span><span class="line"><span class="cl"> expiresDate.setClearButtonVisible(true);</span></span><span class="line"><span class="cl"> expiresDate.setPlaceholder("dd.MM.yyyy");</span></span><span class="line"><span class="cl"> expiresTime.setStep(Duration.ofMinutes(1));</span></span><span class="line"><span class="cl"> expiresTime.setPlaceholder("HH:mm");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Activate time only when a date is set</span></span><span class="line"><span class="cl"> expiresTime.setEnabled(false);</span></span><span class="line"><span class="cl"> expiresDate.addValueChangeListener(ev -&gt; {</span></span><span class="line"><span class="cl"> boolean hasDate = ev.getValue() != null;</span></span><span class="line"><span class="cl"> expiresTime.setEnabled(hasDate<span class="err">&amp;&amp;</span> !noExpiry.getValue());</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> noExpiry.addValueChangeListener(ev -&gt; {</span></span><span class="line"><span class="cl"> boolean disabled = ev.getValue();</span></span><span class="line"><span class="cl"> expiresDate.setEnabled(!disabled);</span></span><span class="line"><span class="cl"> expiresTime.setEnabled(!disabled<span class="err">&amp;&amp;</span> expiresDate.getValue() != null);</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> form.add(noExpiry, expiresDate, expiresTime);</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">private Optional<span class="nt">&lt;Instant&gt;</span> computeExpiresAt() {</span></span><span class="line"><span class="cl"> if (Boolean.TRUE.equals(noExpiry.getValue())) return Optional.empty();</span></span><span class="line"><span class="cl"> LocalDate d = expiresDate.getValue();</span></span><span class="line"><span class="cl"> LocalTime t = expiresTime.getValue();</span></span><span class="line"><span class="cl"> if (d == null || t == null) return Optional.empty();</span></span><span class="line"><span class="cl"> return Optional.of(ZonedDateTime.of(d, t, ZONE).toInstant());</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">private boolean validateExpiryInFuture() {</span></span><span class="line"><span class="cl"> var exp = computeExpiresAt();</span></span><span class="line"><span class="cl"> if (exp.isPresent()<span class="err">&amp;&amp;</span> exp.get().isBefore(Instant.now())) {</span></span><span class="line"><span class="cl"> Notification.show("Expiry must be in the future");</span></span><span class="line"><span class="cl"> return false;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> return true;</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">private void clearForm(Binder<span class="nt">&lt;ShortenRequest&gt;</span> binder) {</span></span><span class="line"><span class="cl"> urlField.clear();</span></span><span class="line"><span class="cl"> aliasField.clear();</span></span><span class="line"><span class="cl"> noExpiry.clear();</span></span><span class="line"><span class="cl"> expiresDate.clear();</span></span><span class="line"><span class="cl"> expiresTime.clear();</span></span><span class="line"><span class="cl"> binder.setBean(new ShortenRequest());</span></span><span class="line"><span class="cl"> urlField.setInvalid(false);</span></span><span class="line"><span class="cl"> aliasField.setInvalid(false);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>It is important to pass it on transparently<strong>to the client</strong>. Instead of storing the time in the UI, it is transmitted to the server in the request. To do this, the CreateView calls the client with the extended signature. The client validates only if an alias is set and passes the data to the server unchanged.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private Optional<span class="nt">&lt;String&gt;</span> createShortCode(ShortenRequest req, Optional<span class="nt">&lt;Instant&gt;</span> expiresAt) {</span></span><span class="line"><span class="cl"> logger().info("createShortCode with ShortenRequest '{}'", req);</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> var customMapping = urlShortenerClient.createCustomMapping(req.getShortURL(), req.getUrl(), expiresAt.orElse(null));</span></span><span class="line"><span class="cl"> return Optional.ofNullable(customMapping.shortCode());</span></span><span class="line"><span class="cl"> } catch (IllegalArgumentException | IOException e) {</span></span><span class="line"><span class="cl"> logger().error("Error saving", e);</span></span><span class="line"><span class="cl"> return Optional.empty();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>On the<strong>client side</strong> , the payload is serialised as a ShortenRequest and sent to the /shorten endpoint. The response is not limited to the shortcode; it is also parsed as a full ShortUrlMapping. As a result, the UI immediately knows the server-side confirmed state – including the expiresAt.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="w"/><span class="nf">createCustomMapping</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">expiredAt</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Create custom mapping alias='{}' url='{}' expiredAt='{}'"</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">expiredAt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">alias</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">alias</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">validate</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">AliasPolicy</span><span class="p">.</span><span class="na">validate</span><span class="p">(</span><span class="n">alias</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">validate</span><span class="p">.</span><span class="na">valid</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">reason</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">validate</span><span class="p">.</span><span class="na">reason</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="n">reason</span><span class="p">.</span><span class="na">defaultMessage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">URL</span><span class="w"/><span class="n">shortenUrl</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serverBaseAdmin</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">PATH_ADMIN_SHORTEN</span><span class="p">).</span><span class="na">toURL</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">HttpURLConnection</span><span class="w"/><span class="n">connection</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">HttpURLConnection</span><span class="p">)</span><span class="w"/><span class="n">shortenUrl</span><span class="p">.</span><span class="na">openConnection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">setRequestMethod</span><span class="p">(</span><span class="s">"POST"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">setDoOutput</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">setRequestProperty</span><span class="p">(</span><span class="n">CONTENT_TYPE</span><span class="p">,</span><span class="w"/><span class="n">JSON_CONTENT_TYPE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">shortenRequest</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ShortenRequest</span><span class="p">(</span><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">expiredAt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">shortenRequest</span><span class="p">.</span><span class="na">toJson</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getOutputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">body</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(</span><span class="n">UTF_8</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">status</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getResponseCode</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">200</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">201</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">is</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">jsonResponse</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">String</span><span class="p">(</span><span class="n">is</span><span class="p">.</span><span class="na">readAllBytes</span><span class="p">(),</span><span class="w"/><span class="n">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">shortUrlMapping</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fromJson</span><span class="p">(</span><span class="n">jsonResponse</span><span class="p">,</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">shortUrlMapping</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">409</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Alias already in use"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Unexpected status: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">status</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>On the<strong>server side</strong> , the ShortenHandler receives the extended request, validates the required fields, and then assigns the store to create it. The response contains the complete mapping object that the client and UI can use immediately.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">final String body = readBody(ex.getRequestBody());</span></span><span class="line"><span class="cl">ShortenRequest req = fromJson(body, ShortenRequest.class);</span></span><span class="line"><span class="cl">if (isNullOrBlank(req.getUrl())) {</span></span><span class="line"><span class="cl"> writeJson(ex, BAD_REQUEST, "Missing 'url'");</span></span><span class="line"><span class="cl"> return;</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">final Result<span class="nt">&lt;ShortUrlMapping&gt;</span> urlMappingResult = store.createMapping(req.getShortURL(), req.getUrl(), req.getExpiresAt());</span></span><span class="line"><span class="cl">urlMappingResult</span></span><span class="line"><span class="cl"> .ifPresentOrElse(success -&gt; logger().info("mapping created success {}", success.toString()),</span></span><span class="line"><span class="cl"> failed -&gt; logger().info("mapping created failed - {}", failed));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">urlMappingResult</span></span><span class="line"><span class="cl"> .ifSuccess(mapping -&gt; {</span></span><span class="line"><span class="cl"> final Headers h = ex.getResponseHeaders();</span></span><span class="line"><span class="cl"> h.add("Location", "/r/" + mapping.shortCode());</span></span><span class="line"><span class="cl"> writeJson(ex, fromCode(201), toJson(mapping));</span></span><span class="line"><span class="cl"> })</span></span><span class="line"><span class="cl"> .ifFailure(errorJson -&gt; {</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> var parsed = JsonUtils.parseJson(errorJson);</span></span><span class="line"><span class="cl"> var errorCode = Integer.parseInt(parsed.get("code"));</span></span><span class="line"><span class="cl"> var message = parsed.get("message");</span></span><span class="line"><span class="cl"> writeJson(ex, fromCode(errorCode), message);</span></span><span class="line"><span class="cl"> } catch (Exception e) {</span></span><span class="line"><span class="cl"> writeJson(ex, CONFLICT, errorJson);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> });</span></span></code></pre></div></div><p>In summary**, a consistent, end-to-end process** is created: The user optionally defines an expiration date when making them, the UI validates basic rules, the client transfers the semantics losslessly, and the server persists them reliably. The OverviewView and the DetailsDialog can then display and interpret this information immediately. This adds a central property to the domain without complicating the existing operating flow.</p><h2 id="navigation-and-package-structure">Navigation and package structure</h2><p>The previous user interface has become much more complex in terms of content due to the detailed dialogue and the extended form functions. To reflect this development, the package structure in the UI module was clearly<strong>reorganised</strong>. The goal was not only a logical grouping according to responsibilities, but also a long-term basis for extended navigation concepts and modular extensions.</p><p>Previously, the OverviewView was still in the general package com.svenruppert.urlshortener.ui.vaadin.views. With the introduction of detailed dialogue and the growing importance of this area in terms of content, it was included in a separate subpackage, &ldquo;<strong>views.overview</strong> &ldquo;, and moved there. This decision not only creates room for additional components (e.g. context menus, helper dialogues or filters), but also follows the principle of<strong>functional coherence</strong> : All classes that together form the overview function are now centrally bundled.</p><p>In the code, this step is reflected in the customisation of the import within the MainLayout :</p><p>old:</p><p>import com.svenruppert.urlshortener.ui.vaadin.views.OverviewView;</p><p>new:</p><p>import com.svenruppert.urlshortener.ui.vaadin.views.overview.OverviewView;</p><p>This small but significant step marks the transition from a purely page-based structure to a component-oriented structure. The MainLayout continues to serve as the central navigation element of the application; the individual views are now more decoupled and can be further developed independently. This creates a clear separation between<strong>layout logic</strong> (central navigation, menus, visual frameworks) and<strong>functional logic</strong> (display, interaction, data flow).</p><p>The route relationships are deliberately kept simple. The OverviewView is still registered under the path /overview and uses the MainLayout as its parent layout element:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@PageTitle</span><span class="w"/><span class="n">Overview</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">OverviewView</span><span class="p">.</span><span class="na">PATH</span><span class="p">,</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainLayout</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">OverviewView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">PATH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"overview"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// ...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This configuration keeps navigation consistent with the other parts of the application, such as CreateView, AboutView, or YoutubeView. New views can be easily added without affecting the central navigation mechanism. This ensures maintainability and scalability – two central requirements for an application that grows gradually within the Advent calendar framework.</p><p>The MainLayout itself is largely unchanged, but has been updated during the package migration to reflect new imports and menu entries. The referencing of the OverviewView is particularly important, as it represents the entry point of user interaction:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">SideNavItem</span><span class="w"/><span class="n">overview</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s2">"Overview"</span><span class="p">,</span><span class="w"/><span class="n">OverviewView</span><span class="p">.</span><span class="k">class</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="n">LIST</span><span class="p">.</span><span class="k">create</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">SideNavItem</span><span class="w"/><span class="k">create</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s2">"Create"</span><span class="p">,</span><span class="w"/><span class="n">CreateView</span><span class="p">.</span><span class="k">class</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="n">PLUS</span><span class="p">.</span><span class="k">create</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">SideNavItem</span><span class="w"/><span class="n">about</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s2">"About"</span><span class="p">,</span><span class="w"/><span class="n">AboutView</span><span class="p">.</span><span class="k">class</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="n">INFO_CIRCLE</span><span class="p">.</span><span class="k">create</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">SideNavItem</span><span class="w"/><span class="n">youtube</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s2">"Youtube"</span><span class="p">,</span><span class="w"/><span class="n">YoutubeView</span><span class="p">.</span><span class="k">class</span><span class="p">,</span><span class="w"/><span class="n">VaadinIcon</span><span class="p">.</span><span class="n">YOUTUBE</span><span class="p">.</span><span class="k">create</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">SideNav</span><span class="w"/><span class="n">nav</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SideNav</span><span class="p">(</span><span class="n">overview</span><span class="p">,</span><span class="w"/><span class="k">create</span><span class="p">,</span><span class="w"/><span class="n">about</span><span class="p">,</span><span class="w"/><span class="n">youtube</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">addToDrawer</span><span class="p">(</span><span class="n">nav</span><span class="p">);</span></span></span></code></pre></div></div><p>This clean separation between routing, structure and presentation lays the foundation for the further development of the user interface. Future features – such as detailed filters, user preferences or administrative functions – can be easily integrated into their own subpackages and namespaces without affecting core navigation. This is the step towards a sustainable UI architecture that specifically supports both growing complexity and extensibility.</p><h2 id="interaction-patterns-and-ux-coherence">Interaction patterns and UX coherence</h2><p>As the application&rsquo;s feature density increases, the importance of a consistent user experience grows. While the first days of the Advent calendar laid the technical foundation, so far, the focus has been on functionality. In this chapter, the focus shifts to the<strong>coherence of interaction patterns</strong> , i.e. how users interact with the application in a consistent, predictable rhythm.</p><p>Central to this is the goal of establishing uniform behaviour for recurring actions. Whether in the overview, in the detail dialogue or in forms – copying, opening or deleting should always feel the same. The application thus conveys reliability, which is particularly crucial for technical users of web-based tools.</p><h3 id="uniform-copying-behaviour">Uniform copying behaviour</h3><p>A good example is<strong>copying URLs and short links</strong>. The exact mechanisms are used in both the grid and the detail dialogue: a button with the VaadinIcon.COPY symbol: an asynchronous clipboard action via JavaScript and a discreet confirmation message. This avoids users having to learn different forms of interaction in various views.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">copy</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">().</span><span class="na">getPage</span><span class="p">().</span><span class="na">executeJs</span><span class="p">(</span><span class="s">"navigator.clipboard.writeText($0)"</span><span class="p">,</span><span class="w"/><span class="n">SHORTCODE_BASE_URL</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Shortcode copied"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Such a detail may seem inconspicuous, but it has a significant impact on the perceived professionalism of the application. The feedback system via<strong>notifications</strong> plays a central role here: The user receives an immediate, unobtrusive signal about the success of his action – a kind of visual receipt that creates trust.</p><h3 id="context-menus-and-multiple-interactions">Context menus and multiple interactions</h3><p>Another element of UX coherence is the<strong>context menu</strong> in the summary table. It allows access to the same actions, which can also be accessed via buttons or double-clicks. This redundant but deliberate multiple interaction follows the principle of<em>user freedom</em> : Users can choose whether to act via direct icons, the keyboard or the context menu.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">GridContextMenu<span class="nt">&lt;ShortUrlMapping&gt;</span> menu = new GridContextMenu<span class="err">&lt;</span>&gt;(grid);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">menu.addItem("Show details", e -&gt; e.getItem().ifPresent(this::openDetailsDialog));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">menu.addItem("Open URL", e -&gt; e.getItem().ifPresent(m -&gt; UI.getCurrent().getPage().open(m.originalUrl(), "_blank")));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">menu.addItem("Copy shortcode", e -&gt; e.getItem().ifPresent(m -&gt; UI.getCurrent().getPage().executeJs("navigator.clipboard.writeText($0)", m.shortCode())));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">menu.addItem("Delete...", e -&gt; e.getItem().ifPresent(m -&gt; confirmDelete(m.shortCode())));</span></span></code></pre></div></div><p>The decision to use context menus is not only aesthetic but also ergonomic: it reduces the grid’s optical density without sacrificing functionality. Actions only appear when they are needed – an approach that is essential in complex management interfaces.</p><h3 id="consistency-of-feedback">Consistency of feedback</h3><p>A uniform pattern is also followed for error messages and validations. The system does not abruptly abort user actions; instead, it clearly communicates why an input was not accepted. Example: The expiration date cannot be in the past. This rule is conveyed both visually and by message.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">exp</span><span class="p">.</span><span class="na">isPresent</span><span class="p">()</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">exp</span><span class="p">.</span><span class="na">get</span><span class="p">().</span><span class="na">isBefore</span><span class="p">(</span><span class="n">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Expiry must be in the future"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Accurate feedback prevents the user from perceiving the system as unpredictable. Every validation, every hint, and every success message follows the same communicative style—short, clear, and polite.</p><h3 id="https-requirement-for-clipboard">HTTPS Requirement for Clipboard</h3><p>A technical but essential detail is that the<strong>Clipboard API</strong> only works in modern browsers within secure contexts (HTTPS or localhost). Therefore, the copy feature is deliberately designed to fail elegantly when clipboard access is unavailable. The application does not crash, but responds silently – an aspect that underlines the robustness and professionalism of the user experience.</p><p>The mechanisms described in this chapter – consistent feedback, redundant operating options and secure fallbacks – together contribute to a coherent user experience. They form the basis of trust and predictability, two characteristics that are essential as software becomes more complex. Overall, the result is an interface that can be operated intuitively, even as its technical depth in the background continues to increase.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/DetailDialog-Part01-SteamPunk-1.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/DetailDialog-Part01-SteamPunk-1.png"/><enclosure url="https://svenruppert.com/images/2025/12/DetailDialog-Part01-SteamPunk-1.png" type="image/jpeg" length="0"/></item><item><title>Advent Calendar - 2025 - Persistence – Part 02</title><link>https://svenruppert.com/posts/advent-calendar-2025-persistence-part-02/</link><pubDate>Fri, 05 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-persistence-part-02/</guid><description>Today, we will finally integrate the StoreIndicator into the UI.
Vaadin integration: live status of the store Implementation of the StoreIndicator Refactoring inside – The MappingCreator as a central logic. EclipseStore – The Persistent Foundation Additional improvements in the core Before &amp; After – Impact on the developer experience The source code for this version can be found on GitHub athttps://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-02</description><content:encoded>&lt;![CDATA[<p>Today, we will finally integrate the StoreIndicator into the UI.</p><ol><li><a href="https://svenruppert.com/2025/12/05/advent-calendar-2025-persistence-part-02/#vaadin-integration-live-status-of-the-store">Vaadin integration: live status of the store</a></li><li><a href="https://svenruppert.com/2025/12/05/advent-calendar-2025-persistence-part-02/#implementation-of-the-storeindicator">Implementation of the StoreIndicator</a></li><li><a href="https://svenruppert.com/2025/12/05/advent-calendar-2025-persistence-part-02/#refactoring-inside-the-mappingcreator-as-a-central-logic">Refactoring inside – The MappingCreator as a central logic.</a></li><li><a href="https://svenruppert.com/2025/12/05/advent-calendar-2025-persistence-part-02/#eclipsestore-the-persistent-foundation">EclipseStore – The Persistent Foundation</a></li><li><a href="https://svenruppert.com/2025/12/05/advent-calendar-2025-persistence-part-02/#additional-improvements-in-the-core">Additional improvements in the core</a></li><li><a href="https://svenruppert.com/2025/12/05/advent-calendar-2025-persistence-part-02/#before-after-impact-on-the-developer-experience">Before &amp; After – Impact on the developer experience</a></li></ol><p><strong>The source code for this version can be found on GitHub at</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-02">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-02</a></p><p>Here&rsquo;s the screenshot of the version we&rsquo;re implementing now.</p><figure><img src="/images/2025/12/image-6.png" alt="Screenshot of the URL Shortener overview page, displaying a table of shortcodes, original URLs, and creation timestamps, with search and filter options." loading="lazy" decoding="async"/><h2 id="vaadin-integration-live-status-of-the-store">Vaadin integration: live status of the store</h2><p>Now that communication between the client and server has been established via the AdminClient, the focus is on integrating with the Vaadin interface. The goal is to make the current system state visible to the user without requiring manual updates. The mechanism is based on cyclic querying, event control, and a reactive UI concept, all fully implemented in Vaadin.</p><p>A central component of this integration is the StoreIndicator component. It serves as a visual interface between the user and the system state. Its design combines UI representation, data retrieval, and state management. The structure is implemented within the Vaadin component model, with the indicator implemented as an independent class, &ldquo;StoreIndicator&rdquo;. This class extends HorizontalLayout and adds several sub-elements to the layout—an icon, a text label, and optional status information. Their behaviour is closely linked to Vaadin&rsquo;s lifecycle control.</p><p>The central entry point is the onAttach method, which is executed whenever the component is added to the UI tree. At this moment, the polling mechanism starts, calling the AdminClient every 10 seconds to get the current status from the server. Communication remains fully controlled on the server side, while the UI is automatically synchronised.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">protected</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">onAttach</span><span class="p">(</span><span class="n">AttachEvent</span><span class="w"/><span class="n">attachEvent</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refreshOnce</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UI</span><span class="w"/><span class="n">ui</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">attachEvent</span><span class="p">.</span><span class="na">getUI</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">setPollInterval</span><span class="p">(</span><span class="n">10000</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">addPollListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">refreshOnce</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Analogous to the onAttach method, onDetach is also an essential part of the StoreIndicator&rsquo;s lifecycle. This method is executed when the component is removed from the UI tree, such as when switching between views or closing the session. Here, the polling mechanism is cleanly terminated, and resources such as event subscriptions or listeners are released. This prevents background processes from continuing to send data to a defunct UI component. The code illustrates this process:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">protected</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">onDetach</span><span class="p">(</span><span class="n">DetachEvent</span><span class="w"/><span class="n">detachEvent</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">super</span><span class="p">.</span><span class="na">onDetach</span><span class="p">(</span><span class="n">detachEvent</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">subscription</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">subscription</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">ignored</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">subscription</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This implementation ensures that no polling tasks or event listeners remain active after the component is removed. This is especially crucial in server-side frameworks like Vaadin to avoid leaks and unnecessary thread activity. The StoreIndicator thus acts like a well-behaved UI citizen, completing its tasks properly when leaving the scene.</p><p>Each polling cycle triggers the refreshOnce method, which uses the AdminClient to determine the current memory state. This checks whether the server returns the mode &ldquo;EclipseStore&rdquo; or &ldquo;InMemory&rdquo;. The colour scheme is adjusted accordingly, and the symbol is highlighted. The design is based on Vaadin&rsquo;s Lumo theme system, which enables clean integration with the application&rsquo;s corporate design via CSS variables.</p><p>Another central element is the internal event system, implemented via the StoreEvents and StoreConnectionChanged classes. It allows for loose coupling between components, so that any change to the memory state can be published globally and consumed by other UI elements. The StoreIndicator uses this system to propagate state changes, while the OverviewView, for example, acts as a subscriber and automatically updates itself when the memory state changes.</p><p>**StoreEvents.publish(new StoreConnectionChanged(newMode, info.mappings()));</p><p>This architecture avoids direct dependencies between UI components. Instead of explicit references or state propagation via view constructors, an event model is used that reduces complexity while increasing the application&rsquo;s responsiveness. The result is a highly modular, reactive UI structure that reflects data changes on the backend.</p><p>The way in which Vaadin is used here as a reactive framework deserves special attention. While classic web frameworks propagate state changes via HTTP requests and complete page reloads, Vaadin uses server-side push to deliver targeted UI updates. In combination with the polling model of the StoreIndicator, a hybrid solution is created: On the one hand, the architecture remains comprehensible and straightforward, and on the other hand, it gives the impression of a live application that reacts to changes in real time.</p><p>This integration of event model, UI lifecycle, and data transfer is an example of modern component-oriented design in the Java ecosystem. It shows that reactive behaviour patterns can also be implemented without external reactive frameworks such as Vert.x or Reactor.</p><h2 id="implementation-of-the-storeindicator">Implementation of the StoreIndicator</h2><p>The indicator&rsquo;s graphical structure is already fully defined in the constructor. It extends HorizontalLayout and uses Vaadin components such as Icon and Span to create a compact, semantically transparent status display. The structure follows the typical Vaadin composition logic: The graphic elements are created, styled and then inserted into the layout via the add method. Particular attention is paid to legibility and visual consistency:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="nf">StoreIndicator</span><span class="p">(</span><span class="n">AdminClient</span><span class="w"/><span class="n">adminClient</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">adminClient</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">adminClient</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setAlignItems</span><span class="p">(</span><span class="n">FlexComponent</span><span class="p">.</span><span class="na">Alignment</span><span class="p">.</span><span class="na">CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setSpacing</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setPadding</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dbIcon</span><span class="p">.</span><span class="na">setSize</span><span class="p">(</span><span class="s">"16px"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dbIcon</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-secondary-text-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">badge</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"font-size"</span><span class="p">,</span><span class="w"/><span class="s">"12px"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"font-weight"</span><span class="p">,</span><span class="w"/><span class="s">"600"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"padding"</span><span class="p">,</span><span class="w"/><span class="s">"0.2rem 0.5rem"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"border-radius"</span><span class="p">,</span><span class="w"/><span class="s">"0.4rem"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"background-color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-contrast-10pct)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-body-text-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">details</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"font-size"</span><span class="p">,</span><span class="w"/><span class="s">"12px"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"opacity"</span><span class="p">,</span><span class="w"/><span class="s">"0.8"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">dbIcon</span><span class="p">,</span><span class="w"/><span class="n">badge</span><span class="p">,</span><span class="w"/><span class="n">details</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The actual logic of the status update is contained in the refreshOnce method. It calls the AdminClient to determine the current server state. This returns a StoreInfo object containing the mode and the number of saved mappings. The mode dynamically adjusts the indicator&rsquo;s colour scheme– green for persistent, blue for volatile, and red for erroneous states. This colour coding is based on Vaadin&rsquo;s Lumo theming and provides immediate visual feedback.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">refreshOnce</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">getUI</span><span class="p">().</span><span class="na">ifPresent</span><span class="p">(</span><span class="n">ui</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">StoreInfo</span><span class="w"/><span class="n">info</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">adminClient</span><span class="p">.</span><span class="na">getStoreInfo</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">persistent</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"EclipseStore"</span><span class="p">.</span><span class="na">equalsIgnoreCase</span><span class="p">(</span><span class="n">info</span><span class="p">.</span><span class="na">mode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">badge</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="n">persistent</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"EclipseStore"</span><span class="p">:</span><span class="w"/><span class="s">"InMemory"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">persistent</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">badge</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"background-color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-success-color-10pct)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-success-text-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dbIcon</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-success-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">badge</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"background-color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-primary-color-10pct)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-primary-text-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dbIcon</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-primary-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">details</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">"· "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">info</span><span class="p">.</span><span class="na">mappings</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" items"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">getElement</span><span class="p">().</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="n">persistent</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"Persistent via EclipseStore"</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"Volatile (InMemory)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">newMode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">persistent</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">StoreMode</span><span class="p">.</span><span class="na">ECLIPSE_STORE</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">StoreMode</span><span class="p">.</span><span class="na">IN_MEMORY</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">newMode</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="n">lastMode</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">lastMode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">newMode</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">StoreEvents</span><span class="p">.</span><span class="na">publish</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">StoreConnectionChanged</span><span class="p">(</span><span class="n">newMode</span><span class="p">,</span><span class="w"/><span class="n">info</span><span class="p">.</span><span class="na">mappings</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">badge</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">"Unavailable"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">badge</span><span class="p">.</span><span class="na">getStyle</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"background-color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-error-color-10pct)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-error-text-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">dbIcon</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"color"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-error-color)"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">details</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">getElement</span><span class="p">().</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"title"</span><span class="p">,</span><span class="w"/><span class="s">"StoreInfo endpoint unavailable"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">lastMode</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="n">StoreMode</span><span class="p">.</span><span class="na">UNAVAILABLE</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">lastMode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">StoreMode</span><span class="p">.</span><span class="na">UNAVAILABLE</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">StoreEvents</span><span class="p">.</span><span class="na">publish</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">StoreConnectionChanged</span><span class="p">(</span><span class="n">StoreMode</span><span class="p">.</span><span class="na">UNAVAILABLE</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This method is an example of reactive UI design without external frameworks. The combination of ui.access() and PollListener allows periodic updates without blocking the main UI thread model. Error cases are elegantly intercepted via the catch block and propagated via the event bus (StoreEvents.publish), so that other components can also react to failures.</p><h2 id="refactoring-inside--the-mappingcreator-as-a-central-logic">Refactoring inside – The MappingCreator as a central logic.</h2><p>With the introduction of EclipseStore and the parallel continued existence of the InMemory store, the need arose to standardise the generation logic for short URLs. Earlier versions of the application included this logic multiple times – both in the InMemory store and in the later EclipseStore implementation. This redundancy not only led to maintenance costs but also made uniform error handling more difficult. The solution to this problem was to introduce a dedicated component, the<strong>MappingCreator,</strong> which acts as a central element between validation, alias policy, and persistence mediation.</p><p>The MappingCreator covers the entire process chain from the alias check to the generation of a new short code to the transfer to persistent storage. This component follows the principle of functional composition and is designed to operate independently of the specific storage technology. This means that both the InMemory and EclipseStore approaches can use the exact generation mechanism without duplicating the logic.</p><p>The class is fully implemented in Core Java and does not require any external libraries. It defines a functional interface, ExistsByCode, that checks whether a specific shortcode already exists, and another interface, PutMapping, that stores a newly created mapping. This makes the Creator a generic tool that can take advantage of any implementation of UrlMappingStore . The following code snippet illustrates the structure:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">MappingCreator</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">ShortCodeGenerator</span><span class="w"/><span class="n">generator</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">ExistsByCode</span><span class="w"/><span class="n">exists</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">PutMapping</span><span class="w"/><span class="n">store</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">clock</span><span class="w"/><span class="n">clock</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Function</span><span class="o">&lt;</span><span class="n">ErrorInfo</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">errorMapper</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">MappingCreator</span><span class="p">(</span><span class="n">ShortCodeGenerator</span><span class="w"/><span class="n">generator</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ExistsByCode</span><span class="w"/><span class="n">exists</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">PutMapping</span><span class="w"/><span class="n">store</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Clock</span><span class="w"/><span class="n">clock</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Function</span><span class="o">&lt;</span><span class="n">ErrorInfo</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">errorMapper</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">generator</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNull</span><span class="p">(</span><span class="n">generator</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">exists</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNull</span><span class="p">(</span><span class="n">exists</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">store</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNull</span><span class="p">(</span><span class="n">store</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">clock</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNullElse</span><span class="p">(</span><span class="n">clock</span><span class="p">,</span><span class="w"/><span class="n">Clock</span><span class="p">.</span><span class="na">systemUTC</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">errorMapper</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">requireNonNull</span><span class="p">(</span><span class="n">errorMapper</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The complete creation process is then displayed in the create method. The process follows a precisely defined scheme: First, it is checked whether the user has entered an alias. If so, it is checked and normalised via the AliasPolicy&rsquo;s validate method. If the alias is invalid, the creator creates an error object with HTTP and application code and returns it as a failure result. If no alias is available, the creator automatically generates a unique shortcode with the ShortCodeGenerator, checks for collisions, and repeats the process if necessary.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public Result<span class="nt">&lt;ShortUrlMapping&gt;</span> create(String alias, String url) {</span></span><span class="line"><span class="cl"> logger().info("createMapping - alias='{}' / url='{}'", alias, url);</span></span><span class="line"><span class="cl"> final String shortCode;</span></span><span class="line"><span class="cl"> if (!isNullOrBlank(alias)) {</span></span><span class="line"><span class="cl"> var aliasCheck = AliasPolicy.validate(alias);</span></span><span class="line"><span class="cl"> if (aliasCheck.failed()) {</span></span><span class="line"><span class="cl"> var reason = aliasCheck.reason();</span></span><span class="line"><span class="cl"> var reasonCode = switch (reason) {</span></span><span class="line"><span class="cl"> case NULL_OR_BLANK -&gt; "ALIAS_EMPTY";</span></span><span class="line"><span class="cl"> case TOO_SHORT -&gt; "ALIAS_TOO_SHORT";</span></span><span class="line"><span class="cl"> case TOO_LONG -&gt; "ALIAS_TOO_LONG";</span></span><span class="line"><span class="cl"> case INVALID_CHARS -&gt; "ALIAS_INVALID_CHARS";</span></span><span class="line"><span class="cl"> case RESERVED -&gt; "ALIAS_RESERVED";</span></span><span class="line"><span class="cl"> };</span></span><span class="line"><span class="cl"> var errorJson = errorMapper.apply(new ErrorInfo("400", reason.defaultMessage, reasonCode));</span></span><span class="line"><span class="cl"> return Result.failure(errorJson);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> var normalized = normalize(alias);</span></span><span class="line"><span class="cl"> if (exists.test(normalized)) {</span></span><span class="line"><span class="cl"> var errorJson = errorMapper.apply(new ErrorInfo("409", "normalizedAlias already in use", "ALIAS_CONFLICT"));</span></span><span class="line"><span class="cl"> return Result.failure(errorJson);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> shortCode = normalized;</span></span><span class="line"><span class="cl"> } else {</span></span><span class="line"><span class="cl"> String gen = normalize(generator.nextCode());</span></span><span class="line"><span class="cl"> while (exists.test(gen)) {</span></span><span class="line"><span class="cl"> gen = normalize(generator.nextCode());</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> shortCode = gen;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> var mapping = new ShortUrlMapping(shortCode, url, Instant.now(clock), Optional.empty());</span></span><span class="line"><span class="cl"> store.accept(mapping);</span></span><span class="line"><span class="cl"> return Result.success(mapping);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This method shows the functional structure of the component as an example. The creator does not create side effects outside its intended interfaces. It communicates exclusively via the function objects exists and store. Error handling is fully integrated into the result tree, allowing a clean separation between success and failure paths. In this way, logic remains deterministic, testable, and reproducible.</p><p>With the introduction of MappingCreator, the codebase has been significantly streamlined. Both the InMemoryUrlMappingStore and the EclipseStoreUrlMappingStore now use the same generation logic, which substantially improves consistency and extensibility. The Creator is thus not only a refactoring result, but also a strategic architectural element that combines functional abstraction, type safety and test-driven development.</p><h2 id="eclipsestore--the-persistent-foundation">EclipseStore – The Persistent Foundation</h2><p>After the internal generation logic has been unified with the MappingCreator, the implementation of the EclipseStoreUrlMappingStore now serves as the basis for permanent storage of the generated short URLs. This class replaces volatile storage with a fully persistent object model that maintains the application&rsquo;s state across reboots. While the InMemory store only kept all mappings in a ConcurrentHashMap, in the EclipseStore they are stored within a serializable object, the so-called DataRoot. This structure serves as an anchor for all stored data elements, ensuring the entire object graph remains consistent at all times.</p><p>The central idea of the EclipseStore approach is to implement storage logic not via relational mappings but via object-oriented persistence. This preserves the application&rsquo;s model in its entirety, without requiring object or database table conversions. The following example shows the definition of the DataRoot:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">DataRoot</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Serial</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">serialVersionUID</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1L</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="o">&gt;</span><span class="w"/><span class="n">mappings</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ConcurrentHashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="o">&gt;</span><span class="w"/><span class="nf">mappings</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">mappings</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In the EclipseStoreUrlMappingStore, when the server starts, it checks whether a root structure already exists. If not, it is recreated and registered as the root node of the memory instance. The following snippet shows the relevant section from the constructor:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">storagePath</span><span class="p">=</span><span class="nc">Paths</span><span class="p">.</span><span class="k">get</span><span class="p">(</span><span class="n">storageDir</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="nc">Files</span><span class="p">.</span><span class="n">createDirectories</span><span class="p">(</span><span class="n">storagePath</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">this</span><span class="p">.</span><span class="n">storage</span><span class="p">=</span><span class="nc">EmbeddedStorage</span><span class="p">.</span><span class="n">start</span><span class="p">(</span><span class="n">storagePath</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">DataRoot</span><span class="n">r</span><span class="p">=</span><span class="p">(</span><span class="n">DataRoot</span><span class="p">)</span><span class="n">storage</span><span class="p">.</span><span class="n">root</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">r</span><span class="o">==</span><span class="k">null</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">storage</span><span class="p">.</span><span class="n">setRoot</span><span class="p">(</span><span class="n">new</span><span class="n">DataRoot</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="n">storage</span><span class="p">.</span><span class="n">storeRoot</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This initialisation ensures that the application has the same persisted state both during a cold boot and after a shutdown. All access to the mappings then takes place directly via the root object. When a new mapping is created, the store calls the storeMappingAndPersist method , which stores the object in the internal map and synchronises the memory.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">storeMappingAndPersist</span><span class="p">(</span><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">m</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">dataRoot</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">DataRoot</span><span class="p">)</span><span class="w"/><span class="n">storage</span><span class="p">.</span><span class="na">root</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">mappings</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">dataRoot</span><span class="p">.</span><span class="na">mappings</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">mappings</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">(),</span><span class="w"/><span class="n">m</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">storage</span><span class="p">.</span><span class="na">store</span><span class="p">(</span><span class="n">mappings</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This immediate write process saves changes immediately. The EclipseStore automatically takes over transaction management at the object level and ensures atomic storage without requiring additional commit logic. This reduces the risk of errors and increases the traceability of the system’s status.</p><p>In addition, the EclipseStoreUrlMappingStore implements all the methods of the UrlMappingStore interface, including find, delete, count, and existsByCode. These methods operate directly on the object graph and use helper classes, such as UrlMappingFilterHelper, to efficiently execute queries and perform sorting. The key difference to the InMemory store is that changes to the data structure take effect immediately in persistent memory and are therefore available again when the application restarts.</p><p>The implementation of the EclipseStoreUrlMappingStore makes it clear that persistence was not designed as an afterthought, but as an integral part of the architecture. The entire data flow – from alias generation to MappingCreator to storage in DataRoot – follows a consistent design. This not only achieves persistence, but also anchors it conceptually: the memory is not an external component, but part of the living object model of the application.</p><h2 id="additional-improvements-in-the-core">Additional improvements in the core</h2><p>Parallel to the introduction of the EclipseStore and the standardisation of the mapping logic, the central helper classes in the core module have also been further developed to ensure more consistent data processing. The focus was not on expanding the range of functions, but on making internal processes more precise. In particular, JsonUtils, UrlMappingFilterHelper, and the internal validation logic of the AliasPolicy have been explicitly revised to improve integration between the REST layer, the UI, and memory.</p><p>The JsonUtils class has been fully refactored to improve the security and robustness of data object serialisation and deserialisation. Instead of resorting to external parsers or frameworks, the class implements a well-defined strategy for simple objects and for nested structures. The focus is on stability, readability and deterministic processing. A central component is the toJsonListingPaped method, which is used for tabular representation in the administration interface. It generates a JSON array from a list of mapping objects, including metadata such as the total number and paging information.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public static String toJsonListingPaged(List<span class="nt">&lt;ShortUrlMapping&gt;</span> list, int total) {</span></span><span class="line"><span class="cl"> var sb = new StringBuilder();</span></span><span class="line"><span class="cl"> sb.append('{');</span></span><span class="line"><span class="cl"> sb.append("\"total\":").append(total).append(',');</span></span><span class="line"><span class="cl"> sb.append("\"items\":[");</span></span><span class="line"><span class="cl"> for (int i = 0; i<span class="nt">&lt; list.size</span><span class="err">();</span><span class="err">i++)</span><span class="err">{</span></span></span><span class="line"><span class="cl"><span class="err">sb.append(toJson(list.get(i)));</span></span></span><span class="line"><span class="cl"><span class="err">if</span><span class="err">(i</span><span class="err">&lt;</span><span class="err">list.size()</span><span class="err">-</span><span class="err">1)</span><span class="err">sb.append(',');</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span><span class="line"><span class="cl"><span class="err">sb.append("]}");</span></span></span><span class="line"><span class="cl"><span class="err">return</span><span class="err">sb.toString();</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span></code></pre></div></div><p>This method exemplifies the class&rsquo;s philosophy: complete control over serialisation and field order. This guarantees that the generated JSON structures correspond precisely to the expected format – an important point when the backend and UI are developed independently of each other. The use of StringBuilder is not a stylistic device, but a deliberate design to minimise garbage objects under high request load.</p><p>Another central component is the UrlMappingFilterHelper, which handles dynamic processing of filter and sorting parameters for REST endpoints. This class abstracts the transformation of the user-entered filters into internal predicate objects, which are then applied to the stored mappings at runtime. The implementation supports flexible queries without requiring specialised query languages or parsers. The following excerpt shows the method that evaluates a URL mapping based on several parameters:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">matches</span><span class="p">(</span><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">m</span><span class="p">,</span><span class="w"/><span class="n">UrlMappingListRequest</span><span class="w"/><span class="n">req</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">codePart</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">m</span><span class="p">.</span><span class="na">shortCode</span><span class="p">().</span><span class="na">contains</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">codePart</span><span class="p">()))</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">urlPart</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">m</span><span class="p">.</span><span class="na">originalUrl</span><span class="p">().</span><span class="na">contains</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">urlPart</span><span class="p">()))</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">from</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">createdAt</span><span class="p">().</span><span class="na">isBefore</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">from</span><span class="p">()))</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">to</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">createdAt</span><span class="p">().</span><span class="na">isAfter</span><span class="p">(</span><span class="n">req</span><span class="p">.</span><span class="na">to</span><span class="p">()))</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This compact logic ensures that filter requests are processed directly at the object level. The system does not have to generate intermediate representations or convert data. The result is efficient, storage-level filtering that scales to large datasets. In combination with the store&rsquo;s advanced counting methods, it provides a high-performance foundation for complex search and administrative functions.</p><p>This area is rounded off by the extension of the AliasPolicy, in particular by the introduction of the helper method failed(). This is used for precise error evaluation and simplifies the control logic in components such as the MappingCreator. Instead of laboriously checking validation results, the developer can directly ask if a validation has failed, and then take fine-grained action based on the return value. This not only improves the readability of the codebase but also the overall system&rsquo;s error resistance.</p><p>The adjustments above make it clear that there is a well-thought-out concept behind the supposed auxiliary classes. They are not marginal components, but supporting pillars of data consistency and system stability. Through targeted optimisations and a clear methodological framework, a core module is created that serves as the basis for the project&rsquo;s further development, both functionally and architecturally.</p><h2 id="before--after--impact-on-the-developer-experience">Before &amp; After – Impact on the developer experience</h2><p>With the integration of EclipseStore and the unification of generation logic via MappingCreator, the developer experience in this project has fundamentally changed. What began as a loose interplay of individual components has developed into a precisely orchestrated system that convinces both in its architecture and maintainability. This is particularly evident in the reduction of redundancies, improved testability and increased transparency in the code flow.</p><p>In the past, central operations – especially the creation and verification of new mappings – were scattered across multiple classes. The InMemory store contained its own validation and checking routines, while later extensions had to reimplement the same logic for persistent storage. With the MappingCreator, this process has now been transformed into a clearly defined, reusable unit. This not only eliminates duplicate code but also the risk of different implementations delivering divergent results. The following excerpt shows an example of how the creation of a mapping was previously realised within the store:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="w"/><span class="nf">create</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">alias</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">shortCode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">alias</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">alias</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">generator</span><span class="p">.</span><span class="na">nextCode</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">map</span><span class="p">.</span><span class="na">containsKey</span><span class="p">(</span><span class="n">shortCode</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalStateException</span><span class="p">(</span><span class="s">"Alias already exists: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">shortCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">mapping</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ShortUrlMapping</span><span class="p">(</span><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">(),</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">empty</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">map</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">shortCode</span><span class="p">,</span><span class="w"/><span class="n">mapping</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">mapping</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This logic was functionally correct, but inflexible. Neither validation nor error objects nor alias rules were built in, and there was no uniform handling of failures or conflicts. With the introduction of MappingCreators and the centralised error structure, this process has been redesigned. The result is a deterministic, controlled, and traceable generation of short URLs, in which every step – from input to persistence – occurs via clearly defined interfaces.</p><p>The second significant difference concerns how the memory is addressed. Previously, the entire application was designed for volatile data retention, which meant that each reboot resulted in a complete loss of the stored mappings. With the EclipseStore, this state has been fundamentally changed. The following code shows how the Store initialises its persistent database when the application starts:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">storagePath</span><span class="p">=</span><span class="nc">Paths</span><span class="p">.</span><span class="k">get</span><span class="p">(</span><span class="n">storageDir</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="nc">Files</span><span class="p">.</span><span class="n">createDirectories</span><span class="p">(</span><span class="n">storagePath</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="k">this</span><span class="p">.</span><span class="n">storage</span><span class="p">=</span><span class="nc">EmbeddedStorage</span><span class="p">.</span><span class="n">start</span><span class="p">(</span><span class="n">storagePath</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">DataRoot</span><span class="n">r</span><span class="p">=</span><span class="p">(</span><span class="n">DataRoot</span><span class="p">)</span><span class="n">storage</span><span class="p">.</span><span class="n">root</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="p">(</span><span class="n">r</span><span class="o">==</span><span class="k">null</span><span class="p">)</span><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="n">storage</span><span class="p">.</span><span class="n">setRoot</span><span class="p">(</span><span class="n">new</span><span class="n">DataRoot</span><span class="p">());</span></span></span><span class="line"><span class="cl"><span class="n">storage</span><span class="p">.</span><span class="n">storeRoot</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>These few lines mark the crucial difference between temporary and permanent system states. The DataRoot&rsquo;s data structure is loaded at startup, changes are immediately synchronised, and the state is maintained across reboots. From a developer&rsquo;s point of view, this means that unit tests, integration tests and runtime behaviour can now consistently access the same database. The difference between development and production disappears because both use the same data flow and persistence logic.</p><p>This standardisation also directly affects the user interface. The Vaadin components, such as OverviewView and StoreIndicator, now obtain their data via clearly defined interfaces that are decoupled from the persistence layer. State changes of the store or new mappings trigger events that are consumed by the UI components, without explicit dependencies. This creates a consistent, reactive system that works coherently from the data source to the surface.</p><p>This development has fundamentally changed how developers interact with code. Bugs are easier to reproduce, tests are more stable, and extensions can be modular. The code is no longer a collection of independent methods, but a structured system in which each component has its place and responsibility.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Parts-02-SteamPunk.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Parts-02-SteamPunk.png"/><enclosure url="https://svenruppert.com/images/2025/12/Parts-02-SteamPunk.png" type="image/jpeg" length="0"/></item><item><title>Advent Calendar - 2025 - Persistence – Part 01</title><link>https://svenruppert.com/posts/advent-calendar-2025-persistence-part-01/</link><pubDate>Thu, 04 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-persistence-part-01/</guid><description>**Visible change: When the UI shows the memory state
With the end of the last day of the Advent calendar, our URL shortener was fully functional: the admin interface could filter, sort, and display data page by page – performant, cleanly typed, and fully implemented in Core Java. But behind this surface lurked an invisible problem: All data existed only in memory. As soon as the server was restarted, the entire database was lost.</description><content:encoded>&lt;![CDATA[<p>**Visible change: When the UI shows the memory state</p><p>With the end of the last day of the Advent calendar, our URL shortener was fully functional: the admin interface could filter, sort, and display data page by page – performant, cleanly typed, and fully implemented in Core Java. But behind this surface lurked an invisible problem:<strong>All data existed only in memory.</strong> As soon as the server was restarted, the entire database was lost.</p><p><strong>The source code for this version can be found on GitHub at</strong><a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-02">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-02</a></p><p>Here&rsquo;s the screenshot of the version we&rsquo;re going to implement now.</p><figure><img src="/images/2025/12/image-6-1024x473.png" alt="Screenshot of the URL shortener overview interface displaying various options including shortcode search, time filters, and a table of shortened links with their details." loading="lazy" decoding="async"/><p>**Why pure in-memory data is reaching its limits</p><p>This volatility was acceptable for early testing, but as the functionality depth increased, clear limits became apparent. In-memory data has three fundamental drawbacks:</p><ul><li><strong>Volatility:</strong> Each reboot resets the state – a behaviour that leads to unusable test and production data in real admin systems.</li><li><strong>Missing history:</strong> Statistical evaluations or comparisons across sessions are impossible because the state is not persisted over time.</li><li><strong>Invisible state:</strong> The user does not see in the UI whether they are working with persistent or volatile memory.</li></ul><p>This meant that the application remained technically correct, but operationally incomplete. A real administration interface not only needs functions, but also<strong>reliability and transparency</strong> about the system status.</p><p>**Motivation for the introduction of EclipseStore</p><p>This is where<strong>EclipseStore</strong> comes into play.The project aims to make a seamless transition from in-memory data structures to persistent object graph storage –<strong>without ORM, without database, without a break in the architecture.</strong><br>
This makes it perfectly in line with the philosophy of this project: type safety, simplicity and complete control over the data flow.</p><p>EclipseStore stores the same object graph that we already use in memory permanently on disk.No mapping, no SQL, no external dependency injection layer – the application remains<em>pure Java</em>. By integrating EclipseStoreUrlMappingStore, the shortener can run in two modes:</p><ul><li><strong>InMemory</strong> for quick tests and volatile sessions,</li><li><strong>EclipseStore</strong> for productive, persistent runtimes.</li></ul><p>This duality not only enables real operational data, but also a clean transition between the development and production environments – an essential aspect if you want to systematically upgrade applications without rewriting them.</p><p>**Goal: Permanent storage and live status in the UI</p><p>But persistence alone was not the goal of this chapter; it was at least as necessary that the condition became<strong>visible</strong>. If the server is in persistent mode, the user interface should show this immediately – preferably in real time.</p><p>This is precisely where the new<strong>status display in the MainLayout</strong> comes in: A small icon with colour feedback (green, blue, red) signals whether the system is currently volatile, persistent or unreachable. Underneath are mechanisms that cyclically query the server state, distribute events, and automatically update the UI when the mode changes.</p><figure><img src="/images/2025/12/image-5.png" alt="Screenshot of the status display in the MainLayout showing three memory states: EclipseStore, Unavailable, and InMemory, with corresponding item counts." loading="lazy" decoding="async"/><p>This merges the backend and frontend into a standard, reactive system: The interface no longer becomes just a window on stored data, but<strong>a visible mirror of the system state</strong>.<br>
This coupling of persistence and UI status forms the foundation for all upcoming enhancements – especially for security, rights management and operational diagnostics.</p><p>**The new MainLayout – context and status at a glance</p><p>After we have created the theoretical basis in the previous chapter and understood why volatile InMemory data is not sufficient and how EclipseStore enables persistence, we now turn our attention to the user interface. Every architectural decision becomes tangible only when it is reflected in the UI, both visually and functionally.</p><p>The most visible expression of this change is in the<strong>MainLayout</strong>. With the introduction of EclipseStore, it received a new, subtle, but highly effective addition: the<strong>StoreIndicator</strong>. This small but central component is located in the application header and shows at a glance whether the shortener is currently using a volatile InMemory store or a persistent EclipseStore.</p><p>This transforms a previously static header into a dynamic control centre that is not only navigative, but also informative – a visual link between the user interface and the system state.</p><p>**Architectural Concept – Visibility as a System Function</p><p>In the original Vaadin interface, the header layout consisted only of a title and the navigation structure. The new component now expands this area: between the title<em>&ldquo;URL Shortener&rdquo;</em> and the menu, an icon with a label appears, whose colour and text change depending on the memory status.</p><p>The concept behind it is deliberately kept minimalist: Status information should not be displayed via dialogues or messages, but should be<strong>permanently present</strong> , similar to a status bar in professional administration tools.</p><p>**Technical integration</p><p>The integration was carried out via the existing MainLayout, which serves as the central layout template in Vaadin. There, a StoreIndicator is added when building the header :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">adminClient</span><span class="p">=</span><span class="nc">AdminClientFactory</span><span class="p">.</span><span class="n">newInstance</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">storeIndicator</span><span class="p">=</span><span class="n">new</span><span class="n">StoreIndicator</span><span class="p">(</span><span class="n">adminClient</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">storeIndicator</span><span class="p">.</span><span class="n">getStyle</span><span class="p">().</span><span class="k">set</span><span class="p">(</span><span class="s2">"margin-left"</span><span class="p">,</span><span class="s2">"auto"</span><span class="p">);</span><span class="n">Align</span><span class="n">Right</span></span></span><span class="line"><span class="cl"><span class="n">HorizontalLayout</span><span class="n">headerRow</span><span class="p">=</span><span class="n">new</span><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">toggle</span><span class="p">,</span><span class="n">appTitle</span><span class="p">,</span><span class="n">new</span><span class="n">Span</span><span class="p">(),</span><span class="n">storeIndicator</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">headerRow</span><span class="p">.</span><span class="n">setWidthFull</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">headerRow</span><span class="p">.</span><span class="n">setAlignItems</span><span class="p">(</span><span class="nc">FlexComponent</span><span class="p">.</span><span class="nc">Alignment</span><span class="p">.</span><span class="n">CENTER</span><span class="p">);</span></span></span></code></pre></div></div><p>The StoreIndicator internally uses an AdminClient that periodically queries the server endpoint /store/info. This provides JSON information about the current mode, the number of saved mappings, and the time of server start.</p><p>**UI design and colour code</p><p>For better orientation, a three-level colour scheme has been introduced:</p><ul><li>🟢<strong>EclipseStore active:</strong> Persistent persistence, data is written to disk.</li><li>🔵<strong>In-Memory Mode:</strong> Volatile memory, suitable for testing and fast development cycles.</li><li>🔴<strong>Error condition:</strong> Connection unreachable or response invalid.</li></ul><p>This colour system is implemented consistently with Vaadin&rsquo;s Lumo design system and ensures that the status indicator blends harmoniously into the UI.</p><p>**Effect on user experience</p><p>The effect is subtle but profound:<br>
Users can immediately recognise whether they are working with a persistent or a temporary system. At the same time, there is greater trust in the application, as the interface now provides transparency into the system’s status. This aspect is often neglected in professional web applications.</p><p>In the next chapter, we will look at how the<strong>StoreIndicator</strong> works technically: from the polling mechanism to the event architecture to its self-updating in the UI.</p><p>**Live Status Component: The StoreIndicator in Detail</p><p>After the visible integration in the MainLayout, let&rsquo;s now turn our attention to the StoreIndicator&rsquo;s actual functionality**.** This central component displays the system’s state in real time. While the header in the UI provides the context, the StoreIndicator is the heart that makes data movement visible and gives immediate feedback to the user.</p><p>**Architecture and Purpose</p><p>The StoreIndicator was designed as a standalone Vaadin component that extends HorizontalLayout. It combines logic, display and event control in a clearly defined unit. Their goal: to regularly retrieve the current memory state (InMemory, EclipseStore or error state) and display it visually.</p><p>To do this, the component uses periodic polling, which queries the server endpoint /store/info via an AdminClient . The response includes metadata such as memory mode, number of saved mappings, and the time of server startup. Based on this data, the display changes dynamically.</p><p>**Lifecycle in the Vaadin UI</p><p>When the user interface is attached (onAttach), polling is activated. The UI retrieves the current system information at fixed intervals (every 10 seconds). When removing (onDetach), this background activity is terminated again, keeping the application resource-efficient.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">protected</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">onAttach</span><span class="p">(</span><span class="n">AttachEvent</span><span class="w"/><span class="n">attachEvent</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">refreshOnce</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">UI</span><span class="w"/><span class="n">ui</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">attachEvent</span><span class="p">.</span><span class="na">getUI</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">ui</span><span class="p">.</span><span class="na">setPollInterval</span><span class="p">(</span><span class="n">10000</span><span class="p">);</span><span class="w"/><span class="n">every</span><span class="w"/><span class="n">10</span><span class="w"/><span class="n">seconds</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">ui</span><span class="p">.</span><span class="na">addPollListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">refreshOnce</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>**Communication with the backend</p><p>Communication between the frontend and the backend occurs via the AdminClient. This calls the JSON endpoint /store/info and converts the response to a StoreInfo object:</p><p>**StoreInfo info = adminClient.getStoreInfo();</p><p>**boolean persistent = &ldquo;EclipseStore&rdquo;.equalsIgnoreCase(info.mode());</p><p>On this basis, color, icon and text are updated. The visual status is based on three states:</p><ul><li>🟢<strong>EclipseStore active</strong> – persistent storage, data is stored on disks.</li><li>🔵<strong>InMemory mode</strong> – volatile storage, ideal for testing and development.</li><li>🔴<strong>Error condition</strong> – Connection lost or server unreachable.</li></ul><p>These states are immediately visible in the UI and follow the colour values of Vaadin&rsquo;s Lumo theme, which ensures a consistent design.</p><p>**Event-Based Update</p><p>In addition to polling, the StoreIndicator relies on an event-based notification system. Via StoreEvents.publish(), it sends a global event whenever the storage state changes. Components such as the OverviewView can subscribe to this event and automatically reload when changes are made.</p><p>**StoreEvents.publish(new StoreConnectionChanged(newMode, info.mappings()));</p><p>This loosely coupled event system avoids direct dependencies between UI components and allows for flexible extensibility – a pattern that can also be applied to other UI areas.</p><p>The StoreIndicator, which displays the system&rsquo;s state in real time, operates resource-efficiently and enhances user confidence in the application. It turns the abstract term &ldquo;persistence mode&rdquo; into a visual experience – a perfect example of how backend information can be elegantly integrated into the UI.</p><p>Cheers Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/Parts-01-SteamPunk.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/Parts-01-SteamPunk.png"/><enclosure url="https://svenruppert.com/images/2025/12/Parts-01-SteamPunk.png" type="image/jpeg" length="0"/></item><item><title>Advent Calendar - 2025 - Filter &amp;amp; Search – Part 02</title><link>https://svenruppert.com/posts/advent-calendar-2025-filter-search-part-02/</link><pubDate>Wed, 03 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-filter-search-part-02/</guid><description>In the previous part, we looked at the implementation on the server side. This part is now about the illustration on the user page.
The source code for the initial state can be found on GitHub under https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-00 .</description><content:encoded>&lt;![CDATA[<p>In the previous part, we looked at the implementation on the server side. This part is now about the illustration on the user page.</p><div class="pull-quote"><p>The source code for the initial state can be found on GitHub under<a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-00">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-00</a> .</p></div><p>The following screenshot shows this state of development.</p><figure><img src="/images/2025/12/image-3-1024x208.png" alt="Screenshot of the URL Shortener overview page, displaying a table of short links with columns for Short Code, Original URL, Created On date, and action buttons for deletion." loading="lazy" decoding="async"/><p>The focus of this Advent calendar day is on the introduction of targeted<strong>filtering, search and paging functionality</strong> on the UI side. The goal is to extend the existing &ldquo;Overview&rdquo; view so that users can search specifically for specific shortcodes or URL fragments, restrict time periods and retrieve the results page by page. All the technical foundations for this have been laid in the previous part.</p><div class="pull-quote"><p>The source code for today&rsquo;s articles can be found on Github under<a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-01">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-01</a> .</p></div><p>The following screenshot shows the state of development that we will reach today.</p><figure><img src="/images/2025/12/image-4-1024x271.png" alt="Screenshot of the ‘URL Shortener’ application overview showing filtering, sorting, and pagination features for managing shortcodes and original URLs." loading="lazy" decoding="async"/><h2 id="integration-into-the-vaadin-ui">Integration into the Vaadin UI</h2><p>After implementing the filtering and paging logic on the server and client side, the focus is now on integration into the user interface. The Vaadin UI acts as the visible layer of the system and is crucial to the user experience. The aim of this chapter is to show how the new functions – filtering, searching and page by page – are brought together in a modern, reactive interface.</p><p>In contrast to the previous parts, which primarily described technical structures, the focus here is on the<strong>interaction between user and system</strong>. The<code>OverviewView</code> serves as a central view through which users communicate directly with the API without having to know technical details.</p><p>The following sections show how the new API endpoints and client methods create a dynamic yet intuitive interface – from data binding and filter logic to visual fine-tuning and ergonomic design.</p><h2 id="overviewview--filtering-searching-and-paging-in-the-vaadin-ui">OverviewView – Filtering, Searching, and Paging in the Vaadin UI</h2><p>With the new server and client functions, the basis for an interactive user interface is now available. This chapter describes the revised<code>OverviewView</code> – the central view of the Vaadin application in which all existing short links can be listed, filtered and managed.</p><p>The original version of the View only showed a complete list of all mappings. In the new version, it has been expanded into a<strong>dynamic, filterable and page-based interface that</strong> communicates<code>directly with the endpoints</code>/list<code>and</code>/list/count.</p><h4 id="structure-of-the-user-interface">Structure of the user interface</h4><p>The<code>OverviewView</code> combines several input elements to capture search and filter criteria:</p><ul><li><strong>Text fields</strong> for shortcode and URL substrings (<code>codePart</code>,<code>urlPart</code>)</li><li><strong>Case-sensitive</strong> checkboxes (<code>codeCase</code>,<code>urlCase</code>)</li><li><strong>Date and time selection</strong> for time periods (<code>fromDate</code>,<code>toDate</code>)</li><li><strong>Dropdowns</strong> for sorting (<code>sortBy</code>,<code>dir</code>)</li><li><strong>Page size</strong> via a numeric input field (<code>pageSize</code>)</li></ul><p>In addition, navigation buttons (<code>Prev</code> /<code>Next</code>) enable side control. All input is transferred to a<code>UrlMappingListRequest object, which is passed to the</code>URLShortenerClient<code>when the view is refreshed</code> .</p><h4 id="central-logic-dataprovider">Central logic: DataProvider</h4><p>The data supply to the grid is provided by a<code>CallbackDataProvider</code>, which reacts dynamically to user interactions. This calls two methods of the client in the background:</p><ol><li><code>**list()**</code> – loads the actual records of the current page.</li><li><code>**listCount()**</code> – determines the total number to control page navigation.</li></ol><p>A simplified excerpt of the initialization:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">dataProvider</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">CallbackDataProvider</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">q</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">req</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">buildFilter</span><span class="p">(</span><span class="n">currentPage</span><span class="p">,</span><span class="w"/><span class="n">pageSize</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">urlShortenerClient</span><span class="p">.</span><span class="na">list</span><span class="p">(</span><span class="n">req</span><span class="p">).</span><span class="na">stream</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">},</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">q</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">baseReq</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">buildFilter</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span><span class="w"/><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">totalCount</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">urlShortenerClient</span><span class="p">.</span><span class="na">listCount</span><span class="p">(</span><span class="n">baseReq</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refreshPageInfo</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">totalCount</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">);</span></span></span></code></pre></div></div><p>This architecture enables the View to react immediately to any change in the filter without having to reload the entire page. The user can conveniently browse through the results, adjust filters or change time periods – the data is loaded live in each case.</p><h4 id="ux-improvements">UX Improvements</h4><p>In addition to the functional enhancement, the user experience has also been improved:</p><ul><li><strong>Responsive layouts:</strong> All filter elements arrange themselves flexibly, even with smaller window sizes.</li><li><strong>Automatic page navigation:</strong> The<code>Prev</code> and<code>Next buttons</code> are automatically deactivated depending on the context.</li><li><strong>Date-time combination:</strong> Separate DatePicker and TimePicker fields keep input precise and intuitive.</li></ul><p>These innovations lead to considerable added value in the daily use of the application: Instead of static tables, there is now an interactive, reactive interface that is both more performant and more ergonomic.</p><p>In the next section, we&rsquo;ll look at the implementation of the paging and filtering logic in detail, including the<code>buildFilter() method</code> and how to handle the parameters in the UI.</p><h2 id="implementation-details-of-the-filter-logic--buildfilter-and-data-binding">Implementation details of the filter logic – buildFilter() and data binding</h2><p>The<code>buildFilter()</code> method forms the heart of the new filter mechanics in the Vaadin interface. It collects all of the user&rsquo;s input—such as text fields, checkboxes, dates, and sorting options—and transfers them to a<code>UrlMappingListRequest</code> object. This request object is then passed on to the client, which uses it to generate the appropriate query parameters for the server.</p><h4 id="structure-of-the-buildfilter-method">Structure of the buildFilter() method</h4><p>The focus is on the builder structure of the<code>UrlMappingListRequest</code>. The method first reads all UI components, checks for empty fields, and creates a consistent filter object from them:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">UrlMappingListRequest</span><span class="w"/><span class="nf">buildFilter</span><span class="p">(</span><span class="n">Integer</span><span class="w"/><span class="n">page</span><span class="p">,</span><span class="w"/><span class="n">Integer</span><span class="w"/><span class="n">size</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">UrlMappingListRequest</span><span class="p">.</span><span class="na">Builder</span><span class="w"/><span class="n">b</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UrlMappingListRequest</span><span class="p">.</span><span class="na">builder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">codePart</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">codePart</span><span class="p">.</span><span class="na">getValue</span><span class="p">().</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">codePart</span><span class="p">(</span><span class="n">codePart</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">codeCaseSensitive</span><span class="p">(</span><span class="n">Boolean</span><span class="p">.</span><span class="na">TRUE</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">codeCase</span><span class="p">.</span><span class="na">getValue</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">urlPart</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">urlPart</span><span class="p">.</span><span class="na">getValue</span><span class="p">().</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">urlPart</span><span class="p">(</span><span class="n">urlPart</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">urlCaseSensitive</span><span class="p">(</span><span class="n">Boolean</span><span class="p">.</span><span class="na">TRUE</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">urlCase</span><span class="p">.</span><span class="na">getValue</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">fromDate</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">fromTime</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">zdt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ZonedDateTime</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="n">fromDate</span><span class="p">.</span><span class="na">getValue</span><span class="p">(),</span><span class="w"/><span class="n">fromTime</span><span class="p">.</span><span class="na">getValue</span><span class="p">(),</span><span class="w"/><span class="n">ZoneId</span><span class="p">.</span><span class="na">systemDefault</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">from</span><span class="p">(</span><span class="n">zdt</span><span class="p">.</span><span class="na">toInstant</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">fromDate</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">zdt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fromDate</span><span class="p">.</span><span class="na">getValue</span><span class="p">().</span><span class="na">atStartOfDay</span><span class="p">(</span><span class="n">ZoneId</span><span class="p">.</span><span class="na">systemDefault</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">from</span><span class="p">(</span><span class="n">zdt</span><span class="p">.</span><span class="na">toInstant</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">toDate</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">toTime</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">zdt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ZonedDateTime</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="n">toDate</span><span class="p">.</span><span class="na">getValue</span><span class="p">(),</span><span class="w"/><span class="n">toTime</span><span class="p">.</span><span class="na">getValue</span><span class="p">(),</span><span class="w"/><span class="n">ZoneId</span><span class="p">.</span><span class="na">systemDefault</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">to</span><span class="p">(</span><span class="n">zdt</span><span class="p">.</span><span class="na">toInstant</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">toDate</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">zdt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">toDate</span><span class="p">.</span><span class="na">getValue</span><span class="p">().</span><span class="na">atTime</span><span class="p">(</span><span class="n">23</span><span class="p">,</span><span class="w"/><span class="n">59</span><span class="p">).</span><span class="na">atZone</span><span class="p">(</span><span class="n">ZoneId</span><span class="p">.</span><span class="na">systemDefault</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">to</span><span class="p">(</span><span class="n">zdt</span><span class="p">.</span><span class="na">toInstant</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">sortBy</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">sortBy</span><span class="p">.</span><span class="na">getValue</span><span class="p">().</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">sort</span><span class="p">(</span><span class="n">sortBy</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">dir</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">dir</span><span class="p">.</span><span class="na">getValue</span><span class="p">().</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">dir</span><span class="p">(</span><span class="n">dir</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">page</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">page</span><span class="p">(</span><span class="n">page</span><span class="p">).</span><span class="na">size</span><span class="p">(</span><span class="n">size</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The method works strictly defensively: empty fields are ignored, optional values are only set if they actually exist. The result is always a valid, well-defined request object.</p><h4 id="data-flow-between-ui-and-server">Data flow between UI and server</h4><p>The generated filter objects are passed<code>to the</code>URLShortenerClient<code> via the CallbackDataProvider</code>. This generates the query string for the HTTP request. This creates a clear, linear chain:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">UI</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">UrlMappingListRequest</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">client</span><span class="w"/><span class="err">→</span><span class="w"/><span class="o">/</span><span class="n">list</span><span class="w"/><span class="n">endpoint</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">filter</span><span class="w"/><span class="err">→</span><span class="w"/><span class="n">results</span></span></span></code></pre></div></div><p>Every change in the UI – be it a new search, a changed page size or a date filter – automatically triggers a refresh. The Grid component then updates itself with the newly filtered data.</p><h4 id="advantages-of-encapsulation">Advantages of encapsulation</h4><p>This structure offers several advantages:</p><ul><li><strong>Reusability:</strong><code>buildFilter()</code> encapsulates all UI logic in one method.</li><li><strong>Testability:</strong> The result can be easily tested in unit tests without the need for Vaadin-specific classes.</li><li><strong>Extensibility:</strong> New filter fields can be easily added as long as they are stored in the builder.</li></ul><p>This clean decoupling keeps the Vaadin interface clutter-free, while at the same time achieving deep integration with the API. The<code>buildFilter()</code> method is thus the central node between user interaction and server-side data logic.</p><h2 id="paging-and-user-interaction--control-display-feedback">Paging and user interaction – control, display, feedback</h2><p>The revised<code>OverviewView</code> offers intuitive control for page-by-page navigation through large result sets. The aim is for users to quickly see<strong>where</strong> they are in the results list,<strong>how many</strong> elements exist in total and<strong>which</strong> interactions are currently possible.</p><h4 id="paging-elements-in-the-ui">Paging elements in the UI</h4><ul><li><strong>Buttons:</strong><code>Prev</code> (to the previous page) and<code>Next</code> (to the next page).</li><li><strong>Page size:</strong><code>IntegerField pageSize</code> with min/max limits and step buttons.</li><li><strong>Status display:</strong><code>pageInfo</code> shows e.g. &ldquo;Page 3 / 12 • 289 total&rdquo;.</li></ul><p>These elements are directly linked to the DataProvider and are updated after each query.</p><h4 id="changing-sides-and-limiting">Changing sides and limiting</h4><p>Clicking on<code>Prev</code>/<code>Next</code> adjusts the current page, then the View reloads the data:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">prevBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">currentPage</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">1</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="o">--</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">nextBtn</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">pageSize</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="n">25</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">maxPage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">ceil</span><span class="p">((</span><span class="kt">double</span><span class="p">)</span><span class="w"/><span class="n">totalCount</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">size</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">currentPage</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">maxPage</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>The page size directly affects the calculation of<code>maxPage</code>. Changes to the<code>pageSize</code> field reset the current page to<code>1</code> and trigger a refresh:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">pageSize</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">grid</span><span class="p">.</span><span class="na">setPageSize</span><span class="p">(</span><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="n">25</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">refresh</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><h4 id="status-indicator-update">Status Indicator Update</h4><p>The<code>refreshPageInfo()</code> method synchronizes buttons and display with the current data state:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">refreshPageInfo</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">ofNullable</span><span class="p">(</span><span class="n">pageSize</span><span class="p">.</span><span class="na">getValue</span><span class="p">()).</span><span class="na">orElse</span><span class="p">(</span><span class="n">25</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">maxPage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">ceil</span><span class="p">((</span><span class="kt">double</span><span class="p">)</span><span class="w"/><span class="n">totalCount</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">size</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">currentPage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">min</span><span class="p">(</span><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">currentPage</span><span class="p">),</span><span class="w"/><span class="n">maxPage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">pageInfo</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">"Page "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">currentPage</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" / "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">maxPage</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" • "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">totalCount</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" total"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">prevBtn</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="n">currentPage</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">nextBtn</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="n">currentPage</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">maxPage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This prevents the navigation from getting into invalid states (e.g.<code>Prev</code> on page 1 or<code>Next</code> after the last page).</p><h4 id="interaction-with-list-and-listcount">Interaction with list() and listCount()</h4><ul><li><strong>Count (</strong><code>**listCount**</code><strong>):</strong> Determines the total amount (<code>totalCount</code>)<strong>without</strong> transferring data that does not need to be displayed.</li><li><strong>Data (</strong><code>**list**</code><strong>):</strong> Loads only the currently visible subset (based on page/size and current filters).</li></ul><p>The combination minimizes network load and ensures consistently fast response times.</p><h4 id="ux-details-and-fault-tolerance">UX details and fault tolerance</h4><ul><li><strong>Disable logic:</strong> Buttons are<code> automatically (de)activated</code> depending on currentPage<code>/</code>maxPage.</li><li><strong>Error handling:</strong> Network or server errors are<code>immediately visible via</code>Notification.show(&ldquo;Loading failed&rdquo;).</li><li><strong>Reset behavior:</strong> The<code>reset</code> button sets filters to default values,<code>currentPage</code> to 1 and triggers<code>refresh().</code></li></ul><p>These mechanisms create a reactive, robust paging experience that remains clear even with very large amounts of data and provides clear feedback to the user.</p><h2 id="ux--styling--visual-and-ergonomic-refinements">UX &amp; styling – visual and ergonomic refinements</h2><p>Now that the functionality of the filtering, search and paging mechanisms has been established, this chapter focuses on the fine-tuning of the user interface. The aim is to improve the user experience of the Vaadin interface without changing the technical structure. The focus is on clarity, responsiveness and consistent visual feedback.</p><h4 id="basic-design-principles">Basic design principles</h4><ol><li><strong>Clarity over complexity</strong> – Each function (e.g., filtering, sorting, paging) should be visually recognizable without overwhelming the user with options.</li><li><strong>Visual grouping</strong> – Logically related items are grouped into horizontal or vertical layouts (e.g., time range, sorting options, search fields).</li><li><strong>Consistent feedback</strong> – Every user action (e.g., changing a filter, changing pages, deleting action) receives direct visual or textual feedback.</li></ol><h4 id="exemplary-adjustments">Exemplary adjustments</h4><h5 id="1-layout-structure">1. Layout structure</h5><p>The input elements for filters and paging have been divided into several layout lines:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">filterRow</span><span class="p">=</span><span class="n">new</span><span class="n">HorizontalLayout</span><span class="p">(</span></span></span><span class="line"><span class="cl"><span class="n">codePart</span><span class="p">,</span><span class="n">codeCase</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="n">urlPart</span><span class="p">,</span><span class="n">urlCase</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="n">fromGroup</span><span class="p">,</span><span class="n">toGroup</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="n">sortBy</span><span class="p">,</span><span class="n">dir</span></span></span><span class="line"><span class="cl"><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">filterRow</span><span class="p">.</span><span class="n">setDefaultVerticalComponentAlignment</span><span class="p">(</span><span class="nc">Alignment</span><span class="p">.</span><span class="n">END</span><span class="p">);</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="k">var</span><span class="py">pagingRow</span><span class="p">=</span><span class="n">new</span><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">prevBtn</span><span class="p">,</span><span class="n">nextBtn</span><span class="p">,</span><span class="n">pageInfo</span><span class="p">,</span><span class="n">pageSize</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">pagingRow</span><span class="p">.</span><span class="n">setDefaultVerticalComponentAlignment</span><span class="p">(</span><span class="nc">Alignment</span><span class="p">.</span><span class="n">CENTER</span><span class="p">);</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="n">add</span><span class="p">(</span><span class="n">filterRow</span><span class="p">,</span><span class="n">pagingRow</span><span class="p">,</span><span class="n">grid</span><span class="p">);</span></span></span></code></pre></div></div><p>This division semantically separates<em>filters</em> and<em>navigation</em> and thus improves visual orientation.</p><h5 id="2-color-and-theme-consistency">2. Color and Theme Consistency</h5><p>The buttons now use Vaadin&rsquo;s own theme variants to convey semantic meaning:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">searchBtn</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_PRIMARY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">resetBtn</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_CONTRAST</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">deleteBtn</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_ERROR</span><span class="p">,</span><span class="w"/><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_TERTIARY</span><span class="p">);</span></span></span></code></pre></div></div><p>This immediately signals to the user which actions are primary, neutral or destructive.</p><h5 id="3-responsive-behavior">3. Responsive behavior</h5><p>By using<code>setWrap(true)</code> and percentage widths, the layout is also optimally displayed on smaller screens. Filter elements automatically wrap into new lines without disturbing the overall impression.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">filterRow</span><span class="p">.</span><span class="na">setWrap</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">filterRow</span><span class="p">.</span><span class="na">setWidthFull</span><span class="p">();</span></span></span></code></pre></div></div><h5 id="4-interactive-status-messages">4. Interactive status messages</h5><p>Error and success messages are communicated via<code>Notification.show().</code> In addition, the affected element can optionally be visually marked – for example, by means of temporary color changes or tooltips.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Short link deleted."</span><span class="p">);</span></span></span></code></pre></div></div><h3 id="bringing-together-function-and-aesthetics">Bringing together function and aesthetics</h3><p>These design and ergonomic fine-tunes make the<code>OverviewView</code> a reactive tool for everyday use. The application not only appears technically sophisticated, but also visually conveys the demand for precision and quality.</p><p>This completes the implementation of the UI for the new filter and search logic – a basis on which later extensions such as caching, auto-suggest or extended sorting logics can be built.</p><h2 id="conclusion-and-outlook">Conclusion and outlook</h2><p>With the implementation of the filter, search and paging functions, the first milestone of the Advent calendar project has been reached. A static overview has become a dynamic, interactive system that allows data to be accessed in a targeted and efficient manner. The basic idea of the project – a solution implemented entirely in Core Java without external frameworks – has been consistently retained.</p><h3 id="review">Review</h3><p>This day showed how a clear separation of responsibilities and clean API design can create a solid basis for more complex functionalities:</p><ul><li><strong>Server-side</strong> : Introduction of the<code>UrlMappingFilter</code> and expansion of the<code>InMemoryUrlMappingStore</code> for structured queries.</li><li><strong>Client-side</strong> : Extension of the<code>URLShortenerClient</code> with type-safe methods for filtering and counting.</li><li><strong>UI side</strong> : Integration of the new features into the Vaadin-based interface with reactive paging, intuitive navigation and clear user guidance.</li></ul><p>The result is a consistent overall system that remains modular, expandable and testable.</p><p>Cheers Sven</p>
]]></content:encoded><category>Java</category><category>Serverside</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/12/ChatGPT-Image-2.-Dez.-2025-00_18_37.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/ChatGPT-Image-2.-Dez.-2025-00_18_37.png"/><enclosure url="https://svenruppert.com/images/2025/12/ChatGPT-Image-2.-Dez.-2025-00_18_37.png" type="image/jpeg" length="0"/></item><item><title>Advent Calendar - 2025 - Filter &amp;amp; Search – Part 01</title><link>https://svenruppert.com/posts/advent-calendar-2025-filter-search-part-01/</link><pubDate>Tue, 02 Dec 2025 07:05:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/advent-calendar-2025-filter-search-part-01/</guid><description>With the Vaadin interface described in [Part III], our URL shortener has a fully functional administration console available for the first time. It allows viewing existing short links in tabular form and managing them manually. But after just a few dozen entries, a clear limit becomes apparent: displaying all saved mappings is neither performant nor user-friendly. An efficient shortener must be able to scale – not only when generating, but also when searching through its data.</description><content:encoded>&lt;![CDATA[<p>With the Vaadin interface described in<a href="https://svenruppert.com/2025/08/15/part-iii-webui-with-vaadin-flow-for-the-url-shortener/">[Part III]</a>, our URL shortener has a fully functional administration console available for the first time. It allows viewing existing short links in tabular form and managing them manually. But after just a few dozen entries, a clear limit becomes apparent: displaying all saved mappings is neither performant nor user-friendly. An efficient shortener must be able to scale – not only when generating, but also when searching through its data.</p><ol><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#architecture-extension">Architecture Extension</a></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#server-side-changes">Server-side changes</a><ol><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#urlmappingfilter-the-new-filter-model">UrlMappingFilter – the new filter model</a></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#listhandler-extended-get-endpoint-list">ListHandler - Extended GET Endpoint (/list)</a></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#listcounthandler-lightweight-counting-endpoint-list-count">ListCountHandler - Lightweight counting endpoint (/list/count)</a></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#inmemoryurlmappingstore-filtering-sorting-and-paginating">InMemoryUrlMappingStore – Filtering, sorting, and paginating</a></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#queryutils-parsing-and-normalising-query-parameters">QueryUtils – Parsing and Normalising Query Parameters</a></li></ol></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#introduction-to-api-endpoints">Introduction to API Endpoints</a><ol><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#structure-of-the-new-endpoints">Structure of the new endpoints</a><ol><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#compatibility-and-stability">Compatibility and stability</a></li></ol></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#supported-parameters">Supported Parameters</a><ol><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#interaction-of-the-parameters">Interaction of the parameters</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#client-side-extensions">Client-side extensions</a><ol><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#urlmappinglistrequest-filter-and-paging-request-builder">UrlMappingListRequest – Filter and Paging Request Builder</a></li></ol></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#extension-of-the-urlshortenerclient-filter-and-count">Extension of the URLShortenerClient – Filter and Count</a><ol><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#new-methods">New methods</a></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#significance-of-enlargement">Significance of enlargement</a></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#backward-compatibility">Backward compatibility</a></li></ol></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#client-test-coverage-urlshortenerclientlisttest">Client Test Coverage – URLShortenerClientListTest</a><ol><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#paging-and-sorting-tests">Paging and sorting tests</a></li><li><a href="https://svenruppert.com/2025/12/02/advent-calendar-2025-filter-search-part-01/#aim-of-the-tests">Aim of the tests</a></li></ol></li></ol><div class="pull-quote"><p>The source code for this status can be found on GitHub at<a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-00">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-00</a>.</p></div><p>The screenshot below shows the status we start with.</p><figure><img src="/images/2025/12/image-1024x208.png" alt="Screenshot of a URL shortener overview page displaying a table with short codes, original URLs, creation dates, and delete action buttons." loading="lazy" decoding="async"/><p>The focus of this first day of the Advent calendar is therefore on introducing targeted<strong>filtering, search, and paging functionality</strong>. The goal is to extend the existing &ldquo;Overview&rdquo; view so that users can search for specific shortcodes or URL fragments, restrict time periods, and retrieve results page by page. All the technical foundations for this have been laid in the previous parts:</p><ol><li><strong>Part I – Short links, clear architecture</strong> : A detailed introduction to the motivation, architecture and data model of the project. The article shows how to develop a URL shortener entirely in Core Java, along with the design principles behind it. (<a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/">https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/</a> )</li><li><strong>Part II – Deepening the Server Components</strong> : This article explores REST handlers, validation, security considerations, and the interaction between the core module and the server layer. (<a href="https://svenruppert.com/2025/06/20/part-ii-urlshortener-first-implementation/">https://svenruppert.com/2025/06/20/part-ii-urlshortener-first-implementation/</a>)</li><li><strong>Part III – The Web UI with Vaadin Flow:</strong> This shows how the user interface is structured, how lists, forms, dialogues and validations work, and how Vaadin Flow enables a modern UI without an additional JavaScript stack. (<a href="https://svenruppert.com/2025/08/15/part-iii-webui-with-vaadin-flow-for-the-url-shortener/">https://svenruppert.com/2025/08/15/part-iii-webui-with-vaadin-flow-for-the-url-shortener/</a>)</li></ol><div class="pull-quote"><p>The source code for today&rsquo;s article can be found on Github under<a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-01">https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-01</a>.</p></div><p>The following screenshot shows the development status shown in it.</p><figure><img src="/images/2025/12/image-1-1024x271.png" alt="Screenshot of the URL Shortener administration console, showing an overview interface. It includes fields for searching shortcodes and original URLs, sorting options, and pagination controls. The table displays existing shortcode mappings with their corresponding original URLs, creation times, and actions." loading="lazy" decoding="async"/><p>It is important that we<strong>do not introduce framework magic</strong> , but consistently expand the existing system with the JDK&rsquo;s on-board resources. Every new class, additional parameter, and API detail follows the same principles as before: clarity, type safety, and transparency. The changes are deliberately incremental – they do not replace existing functions, but expand them in a controlled manner.</p><p>Below, we&rsquo;ll take a step-by-step look at how this new layer was introduced: from the server-side filtering model to the extended REST endpoint and integration with the Vaadin UI. All new code sections are explicitly highlighted so that the differences to the previous parts can be clearly understood.</p><h2 id="architecture-extension">Architecture Extension</h2><p>The module structure established in the previous parts is completely retained:<strong>core</strong> ,<strong>api</strong> ,<strong>client</strong> and<strong>ui-vaadin</strong> continue to form the central layers of the system. However, there is a new logical level between the REST endpoint and the data storage: the<strong>filter model</strong>. It allows you to describe<em>what</em> data the server is supposed to deliver precisely – and no longer just<em>that</em> it returns all existing mappings.</p><p>This architectural principle follows the responsibility separation principle described in [Part II]: Each layer should do precisely what is within its responsibility. The REST API receives requests, converts them into type-safe filter objects, and passes them to the store. The store, in turn, does the actual searching, sorting, and page layout. The UI only uses filtered results, which keeps it lightweight and responsive.</p><p>The new architecture can therefore be understood as a small but decisive extension of the previous data flow:</p><p>[UI] → [Client] → [API] → [Filter Model] → [Store]</p><p>Previously, communication between the UI and the API was mainly in the form of complete data lists; now, queries with parameters are transmitted in a targeted manner. These parameters describe search patterns, time periods, sorting, and paging information. The result is a much more flexible and high-performance interaction that fits seamlessly into the existing system.</p><p>Conceptually, care was taken to ensure that all new components could be integrated<strong>incrementally</strong>. No existing function will be changed or replaced. Instead, clients that continue to use the legacy endpoints (e.g. /list/all) do so unchanged. New clients – such as the revised OverviewView – on the other hand, immediately benefit from the extended filter options.</p><p>This decoupling not only ensures stability during operation but also forms the basis for future extensions, such as persistent filters or server-side search indexes. The following sections now show in concrete terms how this architecture was technically implemented.</p><h2 id="server-side-changes">Server-side changes</h2><p>After the architecture has been defined stably in the previous parts, this section focuses on extending the server-side. The goal is to complement the existing REST endpoints so that they can deliver filtered, sorted, and paginated results in a targeted manner. The previous URL shortener structure has not changed, but has been expanded into clearly defined modules.</p><p>The following subchapters describe the main new building blocks – from the central<code>UrlMappingFilter</code> to the revised handlers and the helper classes that handle parsing and data processing.</p><h3 id="urlmappingfilter--the-new-filter-model">UrlMappingFilter – the new filter model</h3><p>To enable targeted searches, a new class has been introduced: UrlMappingFilter. It forms the heart of the server-side extension and defines all the parameters that precisely describe a query – from text fragments and date ranges to sorting and paging.</p><p>The structure consistently follows the<strong>type-safe API construction principle established in [Part II</strong>]. Instead of evaluating unstructured query parameters directly in the handler, UrlMappingFilter encapsulates all possible filter options in a clearly defined object. This keeps the REST logic lean and the code easier to test.</p><p>A typical filter object might look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">filter</span><span class="p">=</span><span class="nc">UrlMappingFilter</span><span class="p">.</span><span class="n">builder</span><span class="p">()</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">codePart</span><span class="p">(</span><span class="s2">"ex-"</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">urlPart</span><span class="p">(</span><span class="s2">"docs"</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">createdFrom</span><span class="p">(</span><span class="nc">Instant</span><span class="p">.</span><span class="n">parse</span><span class="p">(</span><span class="s2">"2025-10-01T00:00:00Z"</span><span class="p">))</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">limit</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">sortBy</span><span class="p">(</span><span class="nc">SortBy</span><span class="p">.</span><span class="n">CREATED_AT</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">direction</span><span class="p">(</span><span class="nc">Direction</span><span class="p">.</span><span class="n">DESC</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">build</span><span class="p">();</span></span></span></code></pre></div></div><p>Here, only the parameters relevant to a specific query are provided as examples. Unset values remain null or empty, which makes the builder particularly flexible. The class itself is<strong>immutable</strong> – once it has been created, it cannot be changed.</p><p>The most important features at a glance:</p><ul><li>codePart, urlPart: Text-based filtering (substrings, optional case-sensitive)</li><li>createdFrom, createdTo: Time constraint of the result</li><li>offset, limit: Paging control (start index and number of records)</li><li>sortBy, direction: Sort criteria (e.g. CREATED_AT or SHORT_CODE)</li></ul><p>This clear structure allows the store to apply filters efficiently and easily integrate future extensions (such as status or user). It does not replace the existing search mechanism, but expands it modularly. Existing functions such as findAll() are retained.</p><p>The use of an explicit builder pattern ensures that filter objects can only be created in valid combinations. This principle has already proven itself with the ShortenRequest and ShortUrlMapping classes. In addition, consistent use of optional and explicit data types prevents empty strings or incorrect values from interfering with the filtering process.</p><p>This provides a basis for receiving requests in a structured format via the REST API and forwarding them to the store in a targeted manner. The following section shows how these filter objects are used within the ListHandler .</p><h3 id="listhandler---extended-get-endpoint-list">ListHandler - Extended GET Endpoint (/list)</h3><p>The previous implementation of the ListHandler in [Part II] primarily returned all stored mappings as a static list. As the data stock grew, this was neither performant nor differentiated enough. As part of the new filter architecture, the handler has therefore been fundamentally expanded –without changing its previous endpoints. Existing calls such as /list/all or /list/expired will continue to work unchanged.</p><p>The new code path now recognises requests to the /list endpoint and interprets the query parameters. These are transferred to a UrlMappingFilter object and then passed to the store. The store then returns the filtered and sorted subset.</p><p>An excerpt from the new method:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">listFiltered</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">exchange</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">query</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">parseQueryParams</span><span class="p">(</span><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestURI</span><span class="p">().</span><span class="na">getRawQuery</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">page</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">parseIntOrDefault</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"page"</span><span class="p">),</span><span class="w"/><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">clamp</span><span class="p">(</span><span class="n">parseIntOrDefault</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"size"</span><span class="p">),</span><span class="w"/><span class="n">50</span><span class="p">),</span><span class="w"/><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">500</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">offset</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">max</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="n">page</span><span class="w"/><span class="o">-</span><span class="w"/><span class="n">1</span><span class="p">)</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">size</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">sortBy</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">parseSort</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"sort"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">dir</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">parseDir</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"dir"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">codeCase</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Boolean</span><span class="p">.</span><span class="na">parseBoolean</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"codeCase"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">urlCase</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Boolean</span><span class="p">.</span><span class="na">parseBoolean</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"urlCase"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">filter</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UrlMappingFilter</span><span class="p">.</span><span class="na">builder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">codePart</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"code"</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">codeCaseSensitive</span><span class="p">(</span><span class="n">codeCase</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">urlPart</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"url"</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">urlCaseSensitive</span><span class="p">(</span><span class="n">urlCase</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">createdFrom</span><span class="p">(</span><span class="n">parseInstant</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"from"</span><span class="p">),</span><span class="w"/><span class="kc">true</span><span class="p">).</span><span class="na">orElse</span><span class="p">(</span><span class="kc">null</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">createdTo</span><span class="p">(</span><span class="n">parseInstant</span><span class="p">(</span><span class="n">first</span><span class="p">(</span><span class="n">query</span><span class="p">,</span><span class="w"/><span class="s">"to"</span><span class="p">),</span><span class="w"/><span class="kc">false</span><span class="p">).</span><span class="na">orElse</span><span class="p">(</span><span class="kc">null</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">offset</span><span class="p">(</span><span class="n">offset</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">limit</span><span class="p">(</span><span class="n">size</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">sortBy</span><span class="p">(</span><span class="n">sortBy</span><span class="p">.</span><span class="na">orElse</span><span class="p">(</span><span class="kc">null</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">direction</span><span class="p">(</span><span class="n">dir</span><span class="p">.</span><span class="na">orElse</span><span class="p">(</span><span class="kc">null</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">total</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">store</span><span class="p">.</span><span class="na">count</span><span class="p">(</span><span class="n">filter</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">results</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">store</span><span class="p">.</span><span class="na">find</span><span class="p">(</span><span class="n">filter</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">items</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">results</span><span class="p">.</span><span class="na">stream</span><span class="p">().</span><span class="na">map</span><span class="p">(</span><span class="n">m</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">toDto</span><span class="p">(</span><span class="n">m</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="p">.</span><span class="na">now</span><span class="p">())).</span><span class="na">toList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">JsonUtils</span><span class="p">.</span><span class="na">toJsonListingPaged</span><span class="p">(</span><span class="s">"filtered"</span><span class="p">,</span><span class="w"/><span class="n">items</span><span class="p">.</span><span class="na">size</span><span class="p">(),</span><span class="w"/><span class="n">items</span><span class="p">,</span><span class="w"/><span class="n">page</span><span class="p">,</span><span class="w"/><span class="n">size</span><span class="p">,</span><span class="w"/><span class="n">total</span><span class="p">,</span><span class="w"/><span class="n">sortBy</span><span class="p">.</span><span class="na">orElse</span><span class="p">(</span><span class="kc">null</span><span class="p">),</span><span class="w"/><span class="n">dir</span><span class="p">.</span><span class="na">orElse</span><span class="p">(</span><span class="kc">null</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This method illustrates how cleanly the new filter logic integrates into the existing retailer. Instead of returning a complete list, it now creates a<strong>paged response object</strong> that contains additional metadata:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">json</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="nt">"mode"</span><span class="p">:</span><span class="s2">"filtered"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"page"</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"size"</span><span class="p">:</span><span class="mi">25</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"total"</span><span class="p">:</span><span class="mi">123</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"sort"</span><span class="p">:</span><span class="s2">"createdAt"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"dir"</span><span class="p">:</span><span class="s2">"desc"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"count"</span><span class="p">:</span><span class="mi">25</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"items"</span><span class="p">:</span><span class="p">[</span><span class="p">{</span><span class="err">...</span><span class="p">},</span><span class="p">{</span><span class="err">...</span><span class="p">}</span><span class="p">]</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This format offers two decisive advantages: On the one hand, the UI can work specifically with pagination, and on the other hand, API clients can be extended by further parameters in the future without breaking existing structures.</p><p>The trader must continue to pay attention to backward compatibility. All older endpoints are registered in the same context and continue to return complete JSON lists. Only /list uses the extended schema.</p><p>The ListHandler thus becomes the central intermediary between the API request and data filtering – it serves as the &ldquo;translator&rdquo; between HTTP parameters and the internal filter model. The following section shows how the supplemental counting endpoint /list/count uses the same logic for pure quantity queries.</p><h3 id="listcounthandler---lightweight-counting-endpoint-listcount">ListCountHandler - Lightweight counting endpoint (/list/count)</h3><p>In parallel with the extended /list endpoint, an additional handler, ListCountHandler, has been introduced. Its task is to provide only the total number of hits for a given filter configuration – without returning the actual data sets. This separation was deliberately chosen to make paging operations in the UI efficient.</p><p>In contrast to ListHandler, the counting endpoint does not return complete JSON objects with shortcodes and URLs, but only a compact counter value. This allows the client to determine in advance how many pages a query contains before loading individual data pages.</p><p>An excerpt from the implementation shows the simplicity of the approach:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Override</span></span><span class="line"><span class="cl">public void handle(HttpExchange exchange) throws IOException {</span></span><span class="line"><span class="cl"> if (!" GET".equalsIgnoreCase(exchange.getRequestMethod())) {</span></span><span class="line"><span class="cl"> exchange.sendResponseHeaders(405, -1);</span></span><span class="line"><span class="cl"> return;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Map<span class="nt">&lt;String</span><span class="err">,</span><span class="err">List&lt;String</span><span class="nt">&gt;</span>&gt; q = parseQuery(Optional.ofNullable(exchange.getRequestURI().getRawQuery()).orElse(""));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> UrlMappingFilter filter = UrlMappingFilter.builder()</span></span><span class="line"><span class="cl"> .codePart(first(q, "code"))</span></span><span class="line"><span class="cl"> .codeCaseSensitive(bool(q, "codeCase"))</span></span><span class="line"><span class="cl"> .urlPart(first(q, "url"))</span></span><span class="line"><span class="cl"> .urlCaseSensitive(bool(q, "urlCase"))</span></span><span class="line"><span class="cl"> .createdFrom(parseInstant(first(q, "from")).orElse(null))</span></span><span class="line"><span class="cl"> .createdTo(parseInstant(first(q, "to")).orElse(null))</span></span><span class="line"><span class="cl"> .build();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> int total = store.count(filter);</span></span><span class="line"><span class="cl"> byte[] body = ("{\"total\":" + total + "}").getBytes(StandardCharsets.UTF_8);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> exchange.getResponseHeaders().add("Content-Type", "application/json; charset=utf-8");</span></span><span class="line"><span class="cl"> exchange.sendResponseHeaders(200, body.length);</span></span><span class="line"><span class="cl"> try (OutputStream os = exchange.getResponseBody()) {</span></span><span class="line"><span class="cl"> os.write(body);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The method remains minimalist: no paging, no sorting, no additional data. It uses the same parameters as the ListHandler, so both endpoints behave consistently. The only difference is in the return format – a deliberate design for<strong>efficiency and clear accountability</strong>.</p><p>An example of a typical client request:</p><p>GET /list/count?code=ex-&amp;url=docs</p><p>→ { &ldquo;total&rdquo;: 42 }</p><p>The client (e.g. the Vaadin UI) can use this information to calculate the maximum number of pages and dynamically enable or disable the navigation buttons. This prevents unnecessary data transfer when the user only wants to know<em>how many</em> entries a filter currently delivers.</p><p>This lean endpoint lays the foundation for high-performance paging. The next step shows how the store responds internally to these filter requests and efficiently determines the results.</p><h3 id="inmemoryurlmappingstore--filtering-sorting-and-paginating">InMemoryUrlMappingStore – Filtering, sorting, and paginating</h3><p>The InMemoryUrlMappingStore, which already served as a simple storage solution in [Part II], has now been extended by a powerful filter logic. The goal is to process targeted queries directly in memory using the new UrlMappingFilter, including sorting and paging. The code remains easy to understand and test, as no external database is used.</p><p>Compared to the original variant, which provided only findAll(), the new implementation has two central extensions:</p><ul><li><strong>find(UrlMappingFilter filter)</strong> – returns a filtered, sorted, and paginated list of results.</li><li><strong>count(UrlMappingFilter filter)</strong> – determines the number of elements that correspond to a given filter.</li></ul><p>An excerpt shows the core of the new logic:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Override</span></span><span class="line"><span class="cl">public List<span class="nt">&lt;ShortUrlMapping&gt;</span> find(UrlMappingFilter filter) {</span></span><span class="line"><span class="cl"> Objects.requireNonNull(filter, "filter");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> List<span class="nt">&lt;ShortUrlMapping&gt;</span> tmp = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> for (ShortUrlMapping mapping : store.values()) {</span></span><span class="line"><span class="cl"> if (matches(filter, mapping)) tmp.add(mapping);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Comparator<span class="nt">&lt;ShortUrlMapping&gt;</span> cmp = buildComparator(filter);</span></span><span class="line"><span class="cl"> if (cmp != null) {</span></span><span class="line"><span class="cl"> tmp.sort(cmp);</span></span><span class="line"><span class="cl"> if (filter.direction().orElse(Direction.ASC) == Direction.DESC) {</span></span><span class="line"><span class="cl"> Collections.reverse(tmp);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> int from = Math.max(0, filter.offset().orElse(0));</span></span><span class="line"><span class="cl"> int lim = filter.limit().orElse(Integer.MAX_VALUE);</span></span><span class="line"><span class="cl"> if (from &gt;= tmp.size()) return List.of();</span></span><span class="line"><span class="cl"> int to = Math.min(tmp.size(), from + Math.max(0, lim));</span></span><span class="line"><span class="cl"> return tmp.subList(from, to);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The process is divided into three phases:</p><ol><li><strong>Filter</strong> – matches(filter, mapping) checks substrings (with optional case sensitivity) as well as time periods (createdFrom, createdTo).</li><li><strong>Sort</strong> – the Comparator construct allows sorting by CREATED_AT, SHORT_CODE, ORIGINAL_URL, or EXPIRES_AT.</li><li><strong>Paging</strong> – offset and limit return only the requested subsets.</li></ol><p>An example illustrates the effect:</p><p>Filter: codePart=&ldquo;ex-&rdquo;, sortBy=CREATED_AT, direction=DESC, offset=0, limit=5</p><p>Result: The five most recent shortcodes that start with &ldquo;ex-&rdquo;.</p><p>The count(filter) method , on the other hand, is deliberately trivial:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="nf">count</span><span class="p">(</span><span class="n">UrlMappingFilter</span><span class="w"/><span class="n">filter</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">c</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">m</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">store</span><span class="p">.</span><span class="na">values</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">matches</span><span class="p">(</span><span class="n">filter</span><span class="p">,</span><span class="w"/><span class="n">m</span><span class="p">))</span><span class="w"/><span class="n">C</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">c</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This keeps the semantics consistent: count() and find() use the same filter logic, but differ in the return form. This allows pagination information to be reliably calculated.</p><p>A key design goal was backward<strong>compatibility</strong> : the original findAll() method remains unchanged. If no filter is passed, find() can fall back on it internally. Thus, older parts of the application continue to work unchanged.</p><p>The expansion of the store is an example of how more complex search mechanisms can be implemented using simple Java constructs – type-safe, transparent, and without additional dependencies. The following section shows how this logic is specifically supported and validated by QueryUtils.</p><h3 id="queryutils--parsing-and-normalising-query-parameters">QueryUtils – Parsing and Normalising Query Parameters</h3><p>To enable REST endpoints to respond cleanly and robustly to parameter requests, a small helper class, QueryUtils, has been introduced. It converts raw query strings from HTTP requests into type-safe values. This step relieves the handler classes (ListHandler and ListCountHandler) and at the same time ensures uniform behavior when dealing with user parameters.</p><p>The class is located in the package com.svenruppert.urlshortener.api.utils and is purely static. Their purpose is to catch erroneous input, set default values, and convert strings into concrete enums or numbers. This prevents incomplete or incorrect query parameters from causing exceptions or leading to inconsistent states.</p><p>An excerpt from the implementation:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public final class QueryUtils {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private QueryUtils() { }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public static int parseIntOrDefault(Strings s, int def) {</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> return (s == null || s.isBlank()) ? def: Integer.parseInt(s.trim());</span></span><span class="line"><span class="cl"> } catch (NumberFormatException e) {</span></span><span class="line"><span class="cl"> return def;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public static int clamp(int v, int lo, int hi) {</span></span><span class="line"><span class="cl"> return Math.max(lo, Math.min(hi, v));</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public static Optional<span class="nt">&lt;UrlMappingFilter.SortBy&gt;</span> parseSort(Strings) {</span></span><span class="line"><span class="cl"> if (s == null) return Optional.empty();</span></span><span class="line"><span class="cl"> return switch (s.toLowerCase(Locale.ROOT)) {</span></span><span class="line"><span class="cl"> case "createdat" -&gt; Optional.of(UrlMappingFilter.SortBy.CREATED_AT);</span></span><span class="line"><span class="cl"> case "shortcode" -&gt; Optional.of(UrlMappingFilter.SortBy.SHORT_CODE);</span></span><span class="line"><span class="cl"> case "originalurl" -&gt; Optional.of(UrlMappingFilter.SortBy.ORIGINAL_URL);</span></span><span class="line"><span class="cl"> case "expiresat" -&gt; Optional.of(UrlMappingFilter.SortBy.EXPIRES_AT);</span></span><span class="line"><span class="cl"> default -&gt; Optional.empty();</span></span><span class="line"><span class="cl"> };</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public static Optional<span class="nt">&lt;UrlMappingFilter.Direction&gt;</span> parseDir(Strings) {</span></span><span class="line"><span class="cl"> if (s == null) return Optional.empty();</span></span><span class="line"><span class="cl"> return switch (s.toLowerCase(Locale.ROOT)) {</span></span><span class="line"><span class="cl"> case "asc" -&gt; Optional.of(UrlMappingFilter.Direction.ASC);</span></span><span class="line"><span class="cl"> case "desc" -&gt; Optional.of(UrlMappingFilter.Direction.DESC);</span></span><span class="line"><span class="cl"> default -&gt; Optional.empty();</span></span><span class="line"><span class="cl"> };</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"> </span></span></code></pre></div></div><p>This small utility class does several things at once:</p><ul><li><strong>Resilience:</strong> Incorrect or missing parameters never lead to exceptions.</li><li><strong>Consistency:</strong> Sorting and direction are interpreted consistently throughout the system.</li><li><strong>Maintainability:</strong> Traders no longer have to worry about low-level parsing.</li></ul><p>Clamping (clamp(pageSize, 1, 500)) is a crucial security feature: it prevents oversized queries and thus protects both the server and the UI from excessive data volume.</p><p>An example illustrates the practical effect:</p><p>Input: size=-5 → Result: size=1</p><p>Input: size=9999 → Result: size=500</p><p>This makes QueryUtils an inconspicuous but essential part of API robustness. Its features follow the same guiding principle that runs through the entire project:<strong>explicit typing, clear boundaries, and defensive processing.</strong></p><h2 id="introduction-to-api-endpoints">Introduction to API Endpoints</h2><p>Now that the server-side logic has been extended to include filtering, sorting and paging, the REST API itself is now taking centre stage. It serves as the link between the internal functions of the URL shortener and the client&rsquo;s or UI&rsquo;s external access. This chapter aims to describe the new and extended endpoints in detail and explain their functions within the overall system.</p><p>The focus is not only on the data format of the answers, but also on the semantics of the supported parameters. Both aspects are crucial to make filter queries consistent, comprehensible and efficient. Thanks to a clearly structured API, both existing clients can continue to work, and new components – such as the Vaadin-based administration interface – can access subsets of data in a targeted manner without overloading the system.</p><h3 id="structure-of-the-new-endpoints">Structure of the new endpoints</h3><p>With the introduction of filtering and paging, the REST API for the URL shortener has also been extended. In addition to the familiar endpoints from [Part II], two new paths are now available that are specifically designed for targeted queries:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">GET</span><span class="w"/><span class="o">/</span><span class="n">list</span><span class="w"/><span class="err">–</span><span class="w"/><span class="n">returns</span><span class="w"/><span class="n">filtered</span><span class="w"/><span class="n">and</span><span class="w"/><span class="n">paginated</span><span class="w"/><span class="n">results</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GET</span><span class="w"/><span class="o">/</span><span class="n">list</span><span class="o">/</span><span class="n">count</span><span class="w"/><span class="err">–</span><span class="w"/><span class="n">returns</span><span class="w"/><span class="n">only</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">number</span><span class="w"/><span class="n">of</span><span class="w"/><span class="n">hits</span></span></span></code></pre></div></div><p>Both endpoints use the same set of query parameters. While<code>/list</code> returns the actual mappings in structured JSON,<code>/list/count</code> provides a lightweight way to determine the total set for a query. This separation deliberately follows the single-responsibility<strong>principle</strong> and, at the same time, supports high-performance UIs that load pages and large amounts of data.</p><p>A typical call to the new<code>/list</code> endpoint might look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">GET</span><span class="w"/><span class="o">/</span><span class="n">list</span><span class="o">?</span><span class="n">code</span><span class="o">=</span><span class="n">ex</span><span class="o">-&amp;</span><span class="n">url</span><span class="o">=</span><span class="n">docs</span><span class="o">&amp;</span><span class="n">page</span><span class="o">=</span><span class="n">2</span><span class="o">&amp;</span><span class="n">size</span><span class="o">=</span><span class="n">25</span><span class="o">&amp;</span><span class="n">sort</span><span class="o">=</span><span class="n">createdAt</span><span class="o">&amp;</span><span class="n">dir</span><span class="o">=</span><span class="n">desc</span></span></span></code></pre></div></div><p>The result is a JSON object with the following keys:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">json</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="nt">"mode"</span><span class="p">:</span><span class="s2">"filtered"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"page"</span><span class="p">:</span><span class="mi">2</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"size"</span><span class="p">:</span><span class="mi">25</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"total"</span><span class="p">:</span><span class="mi">123</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"sort"</span><span class="p">:</span><span class="s2">"createdAt"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"dir"</span><span class="p">:</span><span class="s2">"desc"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"count"</span><span class="p">:</span><span class="mi">25</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"items"</span><span class="p">:</span><span class="p">[</span></span></span><span class="line"><span class="cl"><span class="p">{</span></span></span><span class="line"><span class="cl"><span class="nt">"shortCode"</span><span class="p">:</span><span class="s2">"ex-beta"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"originalUrl"</span><span class="p">:</span><span class="s2">"https://example.org/blog"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"createdAt"</span><span class="p">:</span><span class="s2">"2025-10-24T12:45:33Z"</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"expiresAt"</span><span class="p">:</span><span class="s2">""</span><span class="p">,</span></span></span><span class="line"><span class="cl"><span class="nt">"Status"</span><span class="p">:</span><span class="s2">"Active"</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span><span class="line"><span class="cl"><span class="p">]</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The JSON structure follows a clear logic: all metadata for the request is in the first fields, while the<code>items</code> array contains the actual results. This makes it easy to integrate the structure into UI components such as tables, grids or data providers.</p><h4 id="compatibility-and-stability">Compatibility and stability</h4><p>Existing endpoints such as<code>/list/all</code>,<code>/list/active,</code> and<code>/list/expired</code> are fully preserved. They continue to provide unchanged responses, so existing clients don&rsquo;t need any customisations. The new endpoints are therefore<strong>integrated additively into</strong> the system.</p><p>For new clients, especially those using the Vaadin UI presented in [Part III], the introduction of these endpoints provides a basis for reactive data display. Instead of loading all mappings at once, the interface can request only the data sets defined by the current filter.</p><p>This API structure lays the foundation for detailing the supported parameters and their meanings in the following subchapters.</p><h3 id="supported-parameters">Supported Parameters</h3><p>The new endpoints<code>/list</code> and<code>/list/count</code> accept a set of query parameters that can be used to formulate targeted search and filter queries. All parameters are optional and can be freely combined. Unset fields do not impose any restrictions.</p><table><thead><tr><th><strong>Parameter</strong></th><th><strong>Type</strong></th><th><strong>Description</strong></th></tr></thead><tbody><tr><td><code>code</code></td><td><code>String</code></td><td>Substring of the short code. Is handled by default<strong>case-insensitive</strong> if<code>codeCase=true</code> is not set.</td></tr><tr><td><code>codeCase</code></td><td><code>Boolean</code></td><td>Controls whether the search for shortcodes should be case-sensitive.</td></tr><tr><td><code>URL</code></td><td><code>String</code></td><td>Substring within the original URL. Similar to<code>code</code>, the case can<code> be controlled</code> via urlCase.</td></tr><tr><td><code>urlCase</code></td><td><code>Boolean</code></td><td>Case sensitivity switch for URL search.</td></tr><tr><td><code>from</code></td><td><code>ISO-8601 timestamp</code></td><td>Lower limit of the creation period (inclusive). Also accepts dates without a time component.</td></tr><tr><td><code>To</code></td><td><code>ISO-8601 timestamp</code></td><td>Upper limit of the creation period (included).</td></tr><tr><td><code>page</code></td><td><code>Int</code></td><td>1-based page number. Default value:<code>1</code>.</td></tr><tr><td><code>Size</code></td><td><code>Int</code></td><td>Number of records per page. Values outside the<code>range 1-500</code> are automatically limited.</td></tr><tr><td><code>sort</code></td><td><code>String</code></td><td>Sort. Allowed are:<code>createdAt</code>,<code>shortCode</code>,<code>originalUrl</code>,<code>expiresAt</code>.</td></tr><tr><td><code>you</code></td><td><code>String</code></td><td>Sorting direction:<code>asc</code> or<code>desc</code>. Default value:<code>asc</code>.</td></tr></tbody></table><p>For example, a complete request might look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">GET</span><span class="w"/><span class="o">/</span><span class="n">list</span><span class="o">?</span><span class="n">code</span><span class="o">=</span><span class="n">ex</span><span class="o">-&amp;</span><span class="n">url</span><span class="o">=</span><span class="n">docs</span><span class="o">&amp;</span><span class="k">from</span><span class="o">=</span><span class="mi">2025</span><span class="o">-</span><span class="mi">10</span><span class="o">-</span><span class="mi">01</span><span class="n">T00</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">00</span><span class="n">Z</span><span class="o">&amp;</span><span class="k">to</span><span class="o">=</span><span class="mi">2025</span><span class="o">-</span><span class="mi">10</span><span class="o">-</span><span class="mi">25</span><span class="n">T23</span><span class="p">:</span><span class="mi">59</span><span class="p">:</span><span class="mi">00</span><span class="n">Z</span><span class="o">&amp;</span><span class="n">page</span><span class="o">=</span><span class="mi">2</span><span class="o">&amp;</span><span class="k">size</span><span class="o">=</span><span class="mi">25</span><span class="o">&amp;</span><span class="n">sort</span><span class="o">=</span><span class="n">createdAt</span><span class="o">&amp;</span><span class="n">dir</span><span class="o">=</span><span class="k">desc</span></span></span></code></pre></div></div><p>The API validates all parameters using the<code>QueryUtils</code> class. Invalid or missing values are replaced with default values to avoid exceptions. This defensive strategy ensures a high level of stability in continuous operation.</p><h4 id="interaction-of-the-parameters">Interaction of the parameters</h4><ul><li>If neither<code>code</code> nor<code>url</code> is set, all mappings are taken into account.</li><li><code>from</code> and<code>to</code> define an<strong>inclusive period</strong> ; both fields may be set independently of each other.</li><li><code>page</code> and<code>size</code> only affect the display of results, not the count in<code>/list/count</code>.</li><li>Combinations of<code>sort</code> and<code>dir</code> have a consistent effect on both endpoints (<code>/list</code> and<code>/list/count</code>).</li></ul><p>A minimalist example of a count-only query is:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">GET</span><span class="w"/><span class="o">/</span><span class="n">list</span><span class="o">/</span><span class="n">count</span><span class="o">?</span><span class="n">url</span><span class="o">=</span><span class="n">example</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="err">→</span><span class="w"/><span class="p">{</span><span class="w"/><span class="s">"total"</span><span class="p">:</span><span class="w"/><span class="n">12</span><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The API is designed so that future parameters can be easily added. Thanks to the central<code>UrlMappingFilter</code>, new fields need only be stored there and taken into account in the builder. This completely preserves the system&rsquo;s extensibility.</p><h2 id="client-side-extensions">Client-side extensions</h2><p>After the server-side has been extended with flexible filtering and paging functions, the client-side customisation now follows. This chapter aims to demonstrate how the new data retrieval capabilities have been integrated into the existing<code>URLShortenerClient</code> without altering its structure or semantics.</p><p>The focus is on<strong>type-safe communication</strong> between the client and the server. Instead of assembling manual query strings or passing loose maps, the new builder<code>UrlMappingListRequest</code> encapsulates all parameters in a clear, object-oriented form. On this basis, filters, sorting and paging can be centrally managed and easily tested.</p><p>The chapter highlights the three main aspects of client extension:</p><ol><li><strong>The new request builder</strong> (<code>UrlMappingListRequest</code>), which cleanly encapsulates filter and paging parameters.</li><li><strong>The advanced methods in the</strong><code>**URLShortenerClient**</code>process these requests and forward them to the new endpoints.</li><li><strong>The associated test class</strong> that secures the interaction between client and server.</li></ol><p>Together, these elements serve as the counterpart to the server-side extensions from the chapter &ldquo;Server-side changes&rdquo; and provide the basis for an interactive user interface that works specifically with filtered and paginated data.</p><h3 id="urlmappinglistrequest--filter-and-paging-request-builder">UrlMappingListRequest – Filter and Paging Request Builder</h3><p>To enable the client to communicate with the new filter and paging endpoints in a targeted manner, the<code>UrlMappingListRequest</code> class was introduced. It acts as a transportable request object that contains all relevant parameters and, if necessary, translates them into a query string for HTTP communication.</p><p>The design follows the principles from [Part II]: no external JSON serialisation, no dependencies on frameworks – instead pure Java logic with a focus on readability and type safety.</p><p>An example illustrates the usage:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">req</span><span class="p">=</span><span class="nc">UrlMappingListRequest</span><span class="p">.</span><span class="n">builder</span><span class="p">()</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">urlPart</span><span class="p">(</span><span class="s2">"docs"</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">page</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">size</span><span class="p">(</span><span class="mi">25</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">sort</span><span class="p">(</span><span class="s2">"createdAt"</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">dir</span><span class="p">(</span><span class="s2">"desc"</span><span class="p">)</span></span></span><span class="line"><span class="cl"><span class="p">.</span><span class="n">build</span><span class="p">();</span></span></span></code></pre></div></div><p>The class then converts this information into a URL query string that can be sent directly to the server:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="o">/</span><span class="n">list</span><span class="o">?</span><span class="n">url</span><span class="o">=</span><span class="n">docs</span><span class="o">&amp;</span><span class="n">page</span><span class="o">=</span><span class="n">2</span><span class="o">&amp;</span><span class="n">size</span><span class="o">=</span><span class="n">25</span><span class="o">&amp;</span><span class="n">sort</span><span class="o">=</span><span class="n">createdAt</span><span class="o">&amp;</span><span class="n">dir</span><span class="o">=</span><span class="n">desc</span></span></span></code></pre></div></div><p>Internally,<code>UrlMappingListRequest</code> consists of a set of simple fields, such as<code>codePart</code>,<code>urlPart</code>,<code>from</code>,<code>to</code>,<code>page</code>,<code>size</code>,<code>sort,</code>and<code>dir</code>. The integrated builder ensures that only valid combinations can be formed. Unset values are automatically ignored during serialisation – empty parameters do not appear in the query string.</p><p>Two methods are central:</p><ul><li><code>**toQueryString()**</code> – generates the complete query string including paging and sorting parameters.</li><li><code>**toQueryStringForCount()**</code> – returns only the filter parameters, without paging or sorting information, for<code>/list/count</code>.</li></ul><p>Both variants are based on an internal helper method that checks parameters, sorts them by key, and<code> securely encodes</code> them via URLEncoder:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toQuery</span><span class="p">(</span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">params</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">params</span><span class="p">.</span><span class="na">entrySet</span><span class="p">().</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">enc</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="na">getKey</span><span class="p">())</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"="</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">enc</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="na">getValue</span><span class="p">()))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">collect</span><span class="p">(</span><span class="n">Collectors</span><span class="p">.</span><span class="na">joining</span><span class="p">(</span><span class="s">"&amp;"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This structure ensures that all parameters are transmitted cleanly and in accordance with the URL format – a detail particularly essential for strings containing special characters (e.g., in URLs).</p><p>Thus,<code>UrlMappingListRequest</code> forms the direct bridge between the client and the API. It does not replace existing calls, but expands the communication options with flexible, type-safe filter queries – in line with the modular system design.</p><h2 id="extension-of-the-urlshortenerclient--filter-and-count">Extension of the URLShortenerClient – Filter and Count</h2><p>On the client side, the<code>URLShortenerClient</code> has been enhanced with two essential features to target the new server-side endpoints:<code>list(UrlMappingListRequest request)</code> and<code>listCount(UrlMappingListRequest request).</code> These methods enable dynamic reaction to filter criteria without manual URL assembly.</p><h4 id="new-methods">New methods</h4><p>The implementation follows the principle of clear separation of responsibilities:<code>list()</code> handles the actual data,<code>listCount()</code> handles the metadata.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public List<span class="nt">&lt;ShortUrlMapping&gt;</span> list(UrlMappingListRequest request)</span></span><span class="line"><span class="cl"> throws IOException {</span></span><span class="line"><span class="cl"> final String json = listAsJson(request);</span></span><span class="line"><span class="cl"> return parseItemsAsMappings(json);</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public int listCount(UrlMappingListRequest req)</span></span><span class="line"><span class="cl"> throws IOException {</span></span><span class="line"><span class="cl"> String qs = (req == null) ? "" : req.toQueryStringForCount();</span></span><span class="line"><span class="cl"> var uri = qs.isEmpty()</span></span><span class="line"><span class="cl"> ? serverBaseAdmin.resolve(PATH_ADMIN_LIST_COUNT)</span></span><span class="line"><span class="cl"> : serverBaseAdmin.resolve(PATH_ADMIN_LIST_COUNT + "?" + qs);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var con = (HttpURLConnection) uri.toURL().openConnection();</span></span><span class="line"><span class="cl"> con.setRequestMethod("GET");</span></span><span class="line"><span class="cl"> con.setRequestProperty("Accept", "application/json");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> int sc = con.getResponseCode();</span></span><span class="line"><span class="cl"> if (sc != 200) {</span></span><span class="line"><span class="cl"> String err = readAllAsString(con.getErrorStream() != null ? con.getErrorStream() : con.getInputStream());</span></span><span class="line"><span class="cl"> throw new IOException("Unexpected HTTP " + sc + " for " + uri + " body=" + err);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> try (var is = con.getInputStream()) {</span></span><span class="line"><span class="cl"> String body = readAllAsString(is);</span></span><span class="line"><span class="cl"> int i = body.indexOf("\"total\"");</span></span><span class="line"><span class="cl"> if (i<span class="nt">&lt; 0</span><span class="err">)</span><span class="err">return</span><span class="err">0;</span></span></span><span class="line"><span class="cl"><span class="err">int</span><span class="na">colon =</span><span class="s">body.indexOf(':',</span><span class="err">i);</span></span></span><span class="line"><span class="cl"><span class="err">int</span><span class="na">end =</span><span class="s">body.indexOf('}',</span><span class="err">colon</span><span class="err">+</span><span class="err">1);</span></span></span><span class="line"><span class="cl"><span class="err">String</span><span class="na">num =</span><span class="s">body.substring(colon</span><span class="err">+</span><span class="err">1,</span><span class="err">end).trim();</span></span></span><span class="line"><span class="cl"><span class="err">return</span><span class="err">Integer.parseInt(num);</span></span></span><span class="line"><span class="cl"><span class="err">}</span><span class="err">finally</span><span class="err">{</span></span></span><span class="line"><span class="cl"><span class="err">con.disconnect();</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span></code></pre></div></div><h4 id="significance-of-enlargement">Significance of enlargement</h4><p>These two methods make the client fully compatible with the advanced server features. The filter logic is completely mapped via the<code>UrlMappingListRequest</code> object, so that the call itself always remains clear:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">var req = UrlMappingListRequest.builder()</span></span><span class="line"><span class="cl"> .codePart("ex-")</span></span><span class="line"><span class="cl"> .urlPart("docs")</span></span><span class="line"><span class="cl"> .page(1)</span></span><span class="line"><span class="cl"> .size(25)</span></span><span class="line"><span class="cl"> .build();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">List<span class="nt">&lt;ShortUrlMapping&gt;</span> results = client.list(req);</span></span><span class="line"><span class="cl">int total = client.listCount(req);</span></span></code></pre></div></div><p>The client can therefore first query the total amount to calculate pagination, then load only the relevant data pages. This significantly reduces the network load, especially with large amounts of data.</p><h4 id="backward-compatibility">Backward compatibility</h4><p>All existing methods such as<code>listAllAsJson()</code> or<code>listActiveAsJson()</code> are preserved. This keeps the API<strong>binary and semantically compatible</strong> – existing tests and applications continue to work. The new functions fit seamlessly into the existing design and, at the same time, form the basis for a reactive UI that can dynamically evaluate filters and paging.</p><h2 id="client-test-coverage--urlshortenerclientlisttest">Client Test Coverage – URLShortenerClientListTest</h2><p>To secure the new client functions, a separate test class, URLShortenerClientListTest, has been introduced. It checks the interaction between the client and a running<code>ShortenerServer</code> and thus serves as an integration test for the entire filter and paging chain.</p><p>The test starts a whole server on a random port and then communicates with the real HTTP endpoint. This ensures that not only the client logic, but also the serialisation, request routing, and server-side filtering work correctly.</p><p>An excerpt from the test case:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Test</span></span><span class="line"><span class="cl">@Order(1)</span></span><span class="line"><span class="cl">void list_all_and_filtered_by_code_and_url_and_date() throws Exception {</span></span><span class="line"><span class="cl"> Arrange</span></span><span class="line"><span class="cl"> var t0 = Instant.now();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> ShortUrlMapping m1 = client.createCustomMapping("ex-alpha", "https://example.com/docs");</span></span><span class="line"><span class="cl"> Thread.sleep(10);</span></span><span class="line"><span class="cl"> ShortUrlMapping m2 = client.createCustomMapping("ex-beta", "https://example.org/blog");</span></span><span class="line"><span class="cl"> Thread.sleep(10);</span></span><span class="line"><span class="cl"> ShortUrlMapping m3 = client.createMapping("https://docs.example.com/page");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var t1 = Instant.now();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Act – test different filters</span></span><span class="line"><span class="cl"> var reqCode = UrlMappingListRequest.builder().codePart("ex-").build();</span></span><span class="line"><span class="cl"> List<span class="nt">&lt;ShortUrlMapping&gt;</span> byCode = client.list(reqCode);</span></span><span class="line"><span class="cl"> assertTrue(byCode.size() &gt;= 2);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var reqUrl = UrlMappingListRequest.builder().urlPart("docs").build();</span></span><span class="line"><span class="cl"> List<span class="nt">&lt;ShortUrlMapping&gt;</span> byUrl = client.list(reqUrl);</span></span><span class="line"><span class="cl"> assertTrue(byUrl.stream().anyMatch(m -&gt; m.originalUrl().contains("docs")));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> var reqDate = UrlMappingListRequest.builder().from(t0).to(t1).build();</span></span><span class="line"><span class="cl"> List<span class="nt">&lt;ShortUrlMapping&gt;</span> byDate = client.list(reqDate);</span></span><span class="line"><span class="cl"> assertFalse(byDate.isEmpty());</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The test examines three central use cases:</p><ol><li><strong>Code filtering</strong> – only shortcodes that start with a specific pattern.</li><li><strong>URL filtering</strong> – URLs that contain specific substrings.</li><li><strong>Time Filtering</strong> – Entries created within a specific time window.</li></ol><h4 id="paging-and-sorting-tests">Paging and sorting tests</h4><p>A second test section focuses on pagination and sorting:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Test</span></span><span class="line"><span class="cl">@Order(2)</span></span><span class="line"><span class="cl">void list_pagination_and_sorting() throws Exception {</span></span><span class="line"><span class="cl"> for (int i = 0; i<span class="nt">&lt; 10</span><span class="err">;</span><span class="err">i++)</span><span class="err">{</span></span></span><span class="line"><span class="cl"><span class="err">client.createMapping("https://example.net/page-"</span><span class="err">+</span><span class="err">i);</span></span></span><span class="line"><span class="cl"><span class="err">Thread.sleep(2);</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="err">var</span><span class="na">req =</span><span class="s">UrlMappingListRequest.builder()</span></span></span><span class="line"><span class="cl"><span class="err">.page(2).size(5)</span></span></span><span class="line"><span class="cl"><span class="err">.sort("createdAt").dir("desc")</span></span></span><span class="line"><span class="cl"><span class="err">.build();</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="err">List&lt;ShortUrlMapping</span><span class="nt">&gt;</span> page2 = client.list(req);</span></span><span class="line"><span class="cl"> assertEquals(5, page2.size());</span></span><span class="line"><span class="cl"> for (int i = 1; i<span class="nt">&lt; page2.size</span><span class="err">();</span><span class="err">i++)</span><span class="err">{</span></span></span><span class="line"><span class="cl"><span class="err">assertTrue(!page2.get(i).createdAt().isAfter(page2.get(i</span><span class="err">-</span><span class="err">1).createdAt()));</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span></code></pre></div></div><p>This checks that page numbering is working correctly and that the results are returned in the expected order.</p><h4 id="aim-of-the-tests">Aim of the tests</h4><p>This test class ensures that the interaction between client and server remains stable, even if parameter combinations change. By using a real<code>HttpServer</code> backend, a realistic environment is simulated, without external dependencies. The result is a high level of trust in the implementation with full coverage of all critical paths.</p><p>The<code>URLShortenerClientListTest</code> class thus concludes the client chapter: It proves that the system as a whole – from filter definition to HTTP communication to result processing – functions consistently.</p><p>In the next part, we will then look at the integration into the Vaadin UI.</p><p>Cheers Sven</p>
]]></content:encoded><category>Design Pattern</category><category>Java</category><category>Serverside</category><media:content url="https://svenruppert.com/images/2025/12/ChatGPT-Image-26.-Okt.-2025-20_38_07.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/12/ChatGPT-Image-26.-Okt.-2025-20_38_07.png"/><enclosure url="https://svenruppert.com/images/2025/12/ChatGPT-Image-26.-Okt.-2025-20_38_07.png" type="image/jpeg" length="0"/></item><item><title>Introduction to the URL‑Shortener Advent Calendar 2025</title><link>https://svenruppert.com/posts/introduction-to-the-url-shortener-advent-calendar-2025/</link><pubDate>Mon, 01 Dec 2025 10:22:35 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/introduction-to-the-url-shortener-advent-calendar-2025/</guid><description>December 2025 is all about a project that has grown steadily in recent months: the Java-based URL Shortener, an open-source project implemented entirely with Core Java, Jetty, and Vaadin Flow. The Advent calendar accompanies users every day with a new feature, a technical deep dive, or an architectural improvement – from the basic data structure and REST handlers to UI components and security aspects.</description><content:encoded>&lt;![CDATA[<p>December 2025 is all about a project that has grown steadily in recent months:<strong>the Java-based URL Shortener, an open-source project implemented entirely with Core Java, Jetty, and Vaadin Flow</strong>. The Advent calendar accompanies users every day with a new feature, a technical deep dive, or an architectural improvement – from the basic data structure and REST handlers to UI components and security aspects.</p><ol><li><a href="https://svenruppert.com/2025/11/30/introduction-to-the-url%E2%80%91shortener-advent-calendar-2025/#motivation-why-your-own-url-shortener">Motivation: Why your own URL shortener?</a></li><li><a href="https://svenruppert.com/2025/11/30/introduction-to-the-url%E2%80%91shortener-advent-calendar-2025/#what-can-users-expect-in-the-advent-calendar">What can users expect in the Advent calendar?</a></li><li><a href="https://svenruppert.com/2025/11/30/introduction-to-the-url%E2%80%91shortener-advent-calendar-2025/#reasons-for-the-choice-of-technology">Reasons for the choice of technology</a><ol><li><a href="https://svenruppert.com/2025/11/30/introduction-to-the-url%E2%80%91shortener-advent-calendar-2025/#core-java-java-24">Core Java (Java 24)</a></li><li><a href="https://svenruppert.com/2025/11/30/introduction-to-the-url%E2%80%91shortener-advent-calendar-2025/#jetty-as-an-embedded-web-server">Jetty as an embedded web server</a></li><li><a href="https://svenruppert.com/2025/11/30/introduction-to-the-url%E2%80%91shortener-advent-calendar-2025/#vaadin-flow-for-the-ui">Vaadin Flow for the UI</a></li><li><a href="https://svenruppert.com/2025/11/30/introduction-to-the-url%E2%80%91shortener-advent-calendar-2025/#eclipsestore-as-a-persistence-solution">EclipseStore as a persistence solution</a></li></ol></li><li><a href="https://svenruppert.com/2025/11/30/introduction-to-the-url%E2%80%91shortener-advent-calendar-2025/#reference-to-previously-published-articles">Reference to previously published articles</a></li><li><a href="https://svenruppert.com/2025/11/30/introduction-to-the-url%E2%80%91shortener-advent-calendar-2025/#why-as-an-advent-calendar">Why, as an Advent calendar?</a></li></ol><h2 id="motivation-why-your-own-url-shortener">Motivation: Why your own URL shortener?</h2><p>The entire project is developed openly on GitHub. There, users will not only find the current state of the code, but also all historical development steps, patches, branches, and PRs discussed in the context of the Advent calendar. The source code can be viewed at<a href="https://github.com/svenruppert/url-shortener">https://github.com/svenruppert/url-shortener.</a></p><p>Commercial URL shorteners are practical – but often non-transparent. They collect usage data, offer proprietary APIs, or block essential functions behind paywalls. The goal of this project is therefore a completely self-powered, transparent, and secure shortener suitable for internal networks, development teams, companies, and hobby projects alike.</p><p>The project pursues three central basic principles: transparency, in which every decision is documented in the code in a comprehensible way; didactics, as the project is not only intended to be a tool, but above all a learning platform; and extensibility, because each feature is designed to be modular and can therefore be easily adapted or expanded.</p><figure><img src="/images/2025/11/Skizze-UrlShortener-01-300x200.png" alt="A diagram illustrating the process of a URL shortener, showing the transformation from a long URL to a short URL." loading="lazy" decoding="async"/><h2 id="what-can-users-expect-in-the-advent-calendar">What can users expect in the Advent calendar?</h2><p>The technical journey through Advent is visible both in terms of content and directly in the repository. Each day is created as its own Git branch, starting with<a href="https://github.com/svenruppert/url-shortener/tree/feature/advent-2025-day-01">feature/advent-2025-day-01</a> and continuing until the final Christmas door. This allows the project&rsquo;s development to be traced in small, clearly defined steps – including all refactorings, architectural adjustments, and new functions. In the same structure, the Advent Calendar presents a precisely outlined feature every day, which is reproduced using the associated patch or PR. Supplementary code excerpts from the core and UI modules, architectural explanations and references to typical anti-patterns deepen the understanding. Each door is rounded off with suitable visualisations and a compact summary, so that the project&rsquo;s development becomes transparent and tangible step by step.</p><h2 id="reasons-for-the-choice-of-technology">Reasons for the choice of technology</h2><p>For the URL shortener, technologies were deliberately chosen that are both<strong>practical</strong> and<strong>didactically valuable</strong>. Every decision supports the project&rsquo;s goal: to create a modern yet easy-to-understand system that requires no unnecessary dependencies.</p><h3 id="core-java-java-24">Core Java (Java 24)</h3><p>The project&rsquo;s basis is pure Core Java. The reasons for this are:</p><p>The use of pure core Java enables maximum transparency: Without any framework magic, users can see exactly how data models, handlers, error handling or serialisation are structured. By deliberately avoiding additional libraries, the attack surface is reduced, and the risk of supply chain problems is minimised. At the same time, this approach makes it very clear to developers how robust and comprehensive the JDK already is today: Many tasks that used to require external dependencies can now be easily solved with onboard tools.</p><h3 id="jetty-as-an-embedded-web-server">Jetty as an embedded web server</h3><p>A deliberate alternative to Spring Boot or Jakarta EE:<br>
Jetty is characterised by its lightweight design and rapid launch time, which are especially beneficial in development scenarios. At the same time, Jetty offers complete control over routing and servlets, so that HTTP mechanics can be precisely demonstrated and implemented in a targeted manner. Thanks to its modular structure, Jetty is ideal for small, well-defined microservices while remaining a stable, proven technology whose long production history speaks for high reliability.</p><p>Jetty offers just the right balance between simplicity and technical relevance for this open source project.</p><h3 id="vaadin-flow-for-the-ui">Vaadin Flow for the UI</h3><p>The project uses Vaadin Flow to implement an entirely server-side, Java-focused UI that does not require an additional JavaScript or TypeScript stack, making it particularly suitable for developers who want to focus entirely on Java. Instead of relying on a complex front-end ecosystem, Vaadin enables end-to-end development in a language, significantly reducing the cognitive load and flattening the learning curve. The component-based architecture enables a productive and structured way of working, in which user interfaces can be clearly modelled and reused. At the same time, server-side rendering eliminates the need for direct REST calls from the browser, increasing security and reducing attack surfaces. Despite this technical simplicity, Vaadin offers modern styling and a user experience that is reminiscent of the professionalism of enterprise applications. This makes the framework ideal for internal tools, administrative interfaces and corporate projects where security, robustness and long-term maintainability are paramount.</p><h3 id="eclipsestore-as-a-persistence-solution">EclipseStore as a persistence solution</h3><p>EclipseStore replaces classic databases with an object-oriented persistence approach that deliberately does not require ORM layers, entities, table models or complex abstractions. Instead of having to convert data into a relational structure, it remains in its natural form: Java records, lists and other object structures are directly persisted. This eliminates the usual mapping logic, significantly reducing the complexity of the persistence layer and making the architecture more straightforward to understand.</p><p>EclipseStore shows its strength especially in small, focused services. Persistence fits seamlessly into the domain model without forcing developers to adapt their data structures to a relational mindset. This direct approach not only leads to more elegant modelling but also to excellent performance. Since EclipseStore manages in-memory-compliant object graphs, data access is high-speed and does not require any additional caching mechanisms or complex optimisations. The result is a lightweight yet powerful persistence system ideal for a modern, compact Java service.</p><p>Didactically, EclipseStore provides valuable insight into how persistent data models can operate when not constrained by relational database constraints. Developers gain a deeper understanding of what object lifecycles, data flows, and modelling decisions look like in an environment that is designed entirely from the Java world. This makes EclipseStore particularly suitable for this URL shortener.</p><h2 id="reference-to-previously-published-articles">Reference to previously published articles</h2><p>Before we get into the Advent calendar, it&rsquo;s worth taking a look at the previous publications, which already present the basics, architecture and the first UI concepts of the project. These articles form the foundation on which the content of the Advent calendar is built:</p><ul><li><strong>Part I – Short links, clear architecture</strong> : A detailed introduction to the motivation, architecture and data model of the project. The article shows how to develop a URL shortener entirely in Core Java, along with the design principles behind it. (<a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/">https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/</a> )</li><li><strong>Part II – Deepening the Server Components</strong> : This article explores REST handlers, validation, security considerations, and the interaction between the core module and the server layer. (<a href="https://svenruppert.com/2025/06/20/part-ii-urlshortener-first-implementation/">https://svenruppert.com/2025/06/20/part-ii-urlshortener-first-implementation/</a>)</li><li><strong>Part III – The Web UI with Vaadin Flow:</strong> This shows how the user interface is structured, how lists, forms, dialogues and validations work, and how Vaadin Flow enables a modern UI without an additional JavaScript stack. (<a href="https://svenruppert.com/2025/08/15/part-iii-webui-with-vaadin-flow-for-the-url-shortener/">https://svenruppert.com/2025/08/15/part-iii-webui-with-vaadin-flow-for-the-url-shortener/</a>)</li></ul><p>These items offer a first technical introduction and are perfect as preparation for the individual doors of the Advent calendar.</p><h2 id="why-as-an-advent-calendar">Why, as an Advent calendar?</h2><p>A project like a URL shortener doesn&rsquo;t happen in a weekend – it consists of many small steps, refactorings, decisions and improvements. An Advent calendar offers the perfect format for this:<strong>short, daily, understandable bites</strong> that come together to form an overall understanding.</p><p>It is, at the same time, a review of the project&rsquo;s journey and a motivating start to the new year: a project that users can run, expand, change, or completely rethink.</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/11/Header-SteamPunk.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/11/Header-SteamPunk.png"/><enclosure url="https://svenruppert.com/images/2025/11/Header-SteamPunk.png" type="image/jpeg" length="0"/></item><item><title>Real-Time in Focus: Server-Sent Events in Core Java without Frameworks</title><link>https://svenruppert.com/posts/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/</link><pubDate>Fri, 05 Sep 2025 08:18:47 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/</guid><description>Chapter 1 – Introduction 1.1 Motivation: Real-time communication without polling In modern applications, it is often necessary to provide new information to the client as quickly as possible. Classic polling , i.e. regularly querying a REST endpoint, is inefficient: it generates unnecessary network traffic and puts a load on both server and client, as requests continue even when there is no new data.</description><content:encoded>&lt;![CDATA[<h2 id="chapter-1--introduction">Chapter 1 – Introduction</h2><h3 id="11-motivation-real-time-communication-without-polling">1.1 Motivation: Real-time communication without polling</h3><p>In modern applications, it is often necessary to provide new information to the client as quickly as possible. Classic<strong>polling</strong> , i.e. regularly querying a REST endpoint, is inefficient: it generates unnecessary network traffic and puts a load on both server and client, as requests continue even when there is no new data.</p><p><strong>Server-sent events (SSE)</strong> offer a resource-saving alternative here. Instead of the client constantly sending new requests, it maintains an open HTTP connection over which the server can send messages at any time. This creates a continuous stream of data with minimal overhead.</p><ol><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#chapter-1-introduction">Chapter 1 – Introduction</a><ol><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#1-1-motivation-real-time-communication-without-polling">1.1 Motivation: Real-time communication without polling</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#1-2-differentiation-from-websockets-and-long-polling">1.2 Differentiation from WebSockets and Long Polling</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#1-3-objective-of-the-article">1.3 Objective of the article</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#chapter-2-the-sse-protocol">Chapter 2 – The SSE Protocol</a><ol><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#2-1-mime-type-text-event-stream">2.1 MIME type text/event-stream</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#2-2-message-structure-event-data-id-and-comments">2.2 Message structure: event:, data:, id: and comments</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#2-3-automatic-reconnect-and-event-ids">2.3 Automatic Reconnect and Event IDs</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#2-4-limitations-utf-8-unidirectionality-proxy-behaviour">2.4 Limitations: UTF-8, Unidirectionality, Proxy Behaviour</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#chapter-3-minimal-rest-sse-server-in-core-java">Chapter 3 – Minimal REST/SSE Server in Core Java</a><ol><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#3-1-implementation-with-httpserver">3.1 Implementation with HttpServer</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#3-2-sending-events-text-comments-ids">3.2 Sending Events (Text, Comments, IDs)</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#3-3-keep-alive-and-connection-stability">3.3 Keep-Alive and Connection Stability</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#3-4-step-by-step-implementation-complete-minimal-example">3.4 Step-by-step implementation – complete minimal example</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#chapter-4-minimal-sse-client-in-core-java">Chapter 4 – Minimal SSE Client in Core Java</a><ol><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#4-1-implementation-with-httpclient">4.1 Implementation with HttpClient</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#4-2-interpreting-events">4.2 Interpreting Events</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#4-3-behaviour-in-case-of-aborting-and-reconnect">4.3 Behaviour in case of aborting and reconnect</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#chapter-5-testing-and-debugging-sse">Chapter 5 – Testing and Debugging SSE</a><ol><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#5-1-using-curl-and-browser-eventsource">5.1 Using curl and browser EventSource</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#5-2-checking-with-java-client">5.2 Checking with Java Client</a></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#5-3-monitoring-and-logging">5.3 Monitoring and logging</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/05/real-time-in-focus-server-sent-events-in-core-java-without-frameworks/#chapter-6-application-scenarios-and-limitations">Chapter 6 – Application Scenarios and Limitations</a></li></ol><h3 id="12-differentiation-from-websockets-and-long-polling">1.2 Differentiation from WebSockets and Long Polling</h3><p>In the context of current communication paradigms, three basic approaches can be distinguished. In so-called<strong>long polling</strong> , the client initiates a request that the server keeps open as long as possible until new information is available. After this has been transmitted, the connection is closed, whereupon the client immediately opens a new request. Although this method appears to be more efficient than classic polling, it remains resource-intensive due to the frequent reinitialization of the connections.</p><p>In contrast,<strong>WebSockets</strong> establish a full-duplex communication relationship. Here, both client and server can exchange messages continuously and bidirectionally. This model offers high flexibility and performance, but it also introduces increased complexity in implementation and operation, often resulting in oversizing when only server-side push messages are needed.</p><p>Server-sent events (SSE)<strong>act as an intermediary approach</strong>. They establish a standardised, unidirectional data stream from the server to the client via a persistent HTTP connection. The native support of current browsers and the comparatively low implementation effort on the server side make SSE a practical option, especially in scenarios where simplicity and resource conservation are paramount.</p><div class="pull-quote"><p><strong>Sourcecode is on GitHub</strong><br><strong><a href="https://github.com/Java-Publications/Blog---Java---Server-Sent-Events-SSE-in-Core-Java-Basics-and-Implementation">https://github.com/Java-Publications/Blog---Java---Server-Sent-Events-SSE-in-Core-Java-Basics-and-Implementation</a></strong></p></div><h3 id="13-objective-of-the-article">1.3 Objective of the article</h3><p>This article provides a basic introduction to<strong>Server-Sent Events (SSE) using Core Java</strong>. The focus is on consistent implementation based on the<strong>Java Development Kit (JDK),</strong> without recourse to external frameworks. The aim is to analyse the specifics of the protocol as well as the minimal implementations on both the server side and the client side in a systematic form and to present them in a comprehensible manner.</p><p>In the course of the study, it is illustrated how an elementary REST/SSE server can be constructed in Java, how a client can receive and process event streams using the Java HttpClient, which typical difficulties arise in the context of testing and debugging, and in which application scenarios the use of SSE offers significant added value.</p><p>Finally, the position of SSE in the area of tension between polling and WebSocket-based methods is critically classified and the transition to the following, practice-oriented implementation chapters is prepared.</p><h2 id="chapter-2--the-sse-protocol">Chapter 2 – The SSE Protocol</h2><h3 id="21-mime-type-textevent-stream">2.1 MIME type<em>text/event-stream</em></h3><p>The foundation of server-sent events is the dedicated MIME type<em>text/event-stream</em>. As soon as a server declares this Content-Type in the HTTP header, it signals to the client that the subsequent data is to be interpreted as a continuous event stream. In contrast to conventional textual response formats, which are closed after complete transmission, SSE deliberately keeps the connection open persistently. This semantic definition creates the basis for a resource-saving push model that can be integrated into existing HTTP infrastructures without proprietary extensions.</p><h3 id="22-message-structureevent--data--id-and-comments">2.2 Message structure:<em>event:</em> ,<em>data:</em> ,<em>id:</em> and comments</h3><p>An SSE data stream is organised on a row-based basis. Each message can consist of multiple fields preceded by a keyword and a colon. The most important fields are:</p><ul><li><strong>event:</strong> defines the type of event that the client can handle specifically.</li><li><strong>data:</strong> contains the actual payload. Consecutive data lines summarise multi-line<em>data</em> fields.</li><li><strong>id:</strong> represents an event ID that is used for resumption after disconnections.</li><li><strong>Comments:</strong> start with a colon and are used both for semantic documentation and to maintain keep-alive signals.</li></ul><p>The delimitation of individual events is done by a blank line (double line breaks), which completes the semantic unit of a message.</p><h3 id="23-automatic-reconnect-and-event-ids">2.3 Automatic Reconnect and Event IDs</h3><p>A significant advantage of SSE over simpler push mechanisms is the integrated reconnect behaviour. If a connection is unexpectedly interrupted, the client automatically initiates a new connection. Using the<em>Last Event ID</em> HTTP header fields or explicit ID assignments in the data stream, it is possible to continue at the exact point where the data stream was interrupted. This principle reduces data loss and facilitates the implementation of robust, state-preserving communication patterns.</p><h3 id="24-limitations-utf-8-unidirectionality-proxy-behaviour">2.4 Limitations: UTF-8, Unidirectionality, Proxy Behaviour</h3><p>Despite its simplicity, the protocol has inherent limitations. On the one hand, the character encoding is strictly set to UTF-8, which means that binary payloads cannot be transmitted directly. In addition, the direction of communication is limited to server-side messages; a bidirectional interaction requires recourse to alternative methods such as WebSockets. Finally, the behaviour of intermediary network nodes – such as proxies or load balancers – can affect the longevity and stability of open HTTP connections, which deserves special attention in production environments.</p><h2 id="chapter-3--minimal-restsse-server-in-core-java">Chapter 3 – Minimal REST/SSE Server in Core Java</h2><h3 id="31-implementation-with-httpserver">3.1 Implementation with<em>HttpServer</em></h3><p>An elementary SSE server can be implemented in Java using the classes included in the JDK. Of particular note is the com.sun.net.httpserver.HttpServer class, which allows HTTP endpoints to be provided without external dependencies. Such a server can be created with a few lines of code, bound to a port number and extended by handlers that process incoming requests.</p><p>A minimal basic framework looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">HttpServer</span><span class="w"/><span class="n">server</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">HttpServer</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">InetSocketAddress</span><span class="p">(</span><span class="n">8080</span><span class="p">),</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">server</span><span class="p">.</span><span class="na">createContext</span><span class="p">(</span><span class="s">"/events"</span><span class="p">,</span><span class="w"/><span class="n">exchange</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseHeaders</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span><span class="w"/><span class="s">"text/event-stream"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">200</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseBody</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">//This is where the events will be written later</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">server</span><span class="p">.</span><span class="na">start</span><span class="p">();</span></span></span></code></pre></div></div><p>It is crucial to set the content type to<em>text/event-stream</em> and not to close the HTTP connection immediately. This creates the basis for continuously transmitting event data to the client.</p><h3 id="32-sending-events-text-comments-ids">3.2 Sending Events (Text, Comments, IDs)</h3><p>The server-side logic for transmitting messages follows the formats defined in the protocol. A typical message can contain both a<em>data:</em> field with payload and optional metadata. Comments are beneficial for realising keep-alive signals.</p><p>A simple example of how to transmit messages:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">PrintWriter</span><span class="w"/><span class="n">writer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">PrintWriter</span><span class="p">(</span><span class="n">os</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">writer</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"id: 1"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">writer</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"event: greeting"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">writer</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"data: Hello client!"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">writer</span><span class="p">.</span><span class="na">println</span><span class="p">();</span><span class="w"/><span class="n">End</span><span class="w"/><span class="n">message</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">writer</span><span class="p">.</span><span class="na">flush</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">//Comment as a heartbeat</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">writer</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">": keep-alive"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">writer</span><span class="p">.</span><span class="na">println</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">writer</span><span class="p">.</span><span class="na">flush</span><span class="p">();</span></span></span></code></pre></div></div><p>Here, a message is transmitted with ID, event type, and data. The double line break concludes the semantic unit. The comment is to maintain the connection.</p><h3 id="33-keep-alive-and-connection-stability">3.3 Keep-Alive and Connection Stability</h3><p>A critical aspect of running SSE servers is ensuring connection stability. HTTP connections can be terminated by inactivity or by restrictive network infrastructures. To avoid this, it is common practice to periodically send comments or blank messages that are ignored by the client but registered as activity by the network. This technique, often referred to as heartbeat or keep-alive, helps keep the connection stable for extended periods of time.</p><p>A robust implementation should also ensure that there are no resource leaks when the connection is lost. In particular, this means that OutputStreams must be reliably closed and background threads must be terminated in a controlled manner.</p><p>This step-by-step implementation creates a functional SSE server that meets the fundamental protocol requirements. In the following chapters, this is built on by developing a corresponding client and examining the robustness of the communication.</p><h3 id="34-step-by-step-implementation--complete-minimal-example">3.4 Step-by-step implementation – complete minimal example</h3><p>In the following, an executable minimal server is set up in clearly defined steps. The implementation uses only the JDK (no external dependencies) and relies on com.sun.net.httpserver.HttpServer.</p><p>**Create and start the server</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.svenruppert.sse</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.sun.net.httpserver.HttpServer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.svenruppert.dependencies.core.logger.HasLogger</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.InetSocketAddress</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">java.nio.charset.StandardCharsets.UTF_8</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SseServer</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">protected</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">PATH_SSE</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"/sse"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">volatile</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">sendMessages</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">volatile</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">shutdownMessage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// Reference so we can properly stop the server from the CLI</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="n">HttpServer</span><span class="w"/><span class="n">server</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">start</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kt">int</span><span class="w"/><span class="n">port</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">8080</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">server</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">HttpServer</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">InetSocketAddress</span><span class="p">(</span><span class="n">port</span><span class="p">),</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">// Background thread for CLI control (start | stop | shutdown)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">Thread</span><span class="w"/><span class="n">cli</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Thread</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">BufferedReader</span><span class="w"/><span class="n">console</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">BufferedReader</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">InputStreamReader</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="na">in</span><span class="p">)))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">String</span><span class="w"/><span class="n">line</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">while</span><span class="w"/><span class="p">((</span><span class="n">line</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">console</span><span class="p">.</span><span class="na">readLine</span><span class="p">())</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">String</span><span class="w"/><span class="n">cmd</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">line</span><span class="p">.</span><span class="na">trim</span><span class="p">().</span><span class="na">toLowerCase</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="k">switch</span><span class="w"/><span class="p">(</span><span class="n">cmd</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">case</span><span class="w"/><span class="s">"start"</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">cmdStart</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">case</span><span class="w"/><span class="s">"stop"</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">cmdStop</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">case</span><span class="w"/><span class="s">"shutdown"</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">cmdShutdown</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">default</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Unknown command: {} (allowed: start | stop | shutdown)"</span><span class="p">,</span><span class="w"/><span class="n">cmd</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">ioe</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">logger</span><span class="p">().</span><span class="na">warn</span><span class="p">(</span><span class="s">"CLI control terminated: {}"</span><span class="p">,</span><span class="w"/><span class="n">ioe</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">},</span><span class="w"/><span class="s">"cli-control"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">cli</span><span class="p">.</span><span class="na">setDaemon</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">cli</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">server</span><span class="p">.</span><span class="na">createContext</span><span class="p">(</span><span class="n">PATH_SSE</span><span class="p">,</span><span class="w"/><span class="n">exchange</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">"GET"</span><span class="p">.</span><span class="na">equalsIgnoreCase</span><span class="p">(</span><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestMethod</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">405</span><span class="p">,</span><span class="w"/><span class="o">-</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseHeaders</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span><span class="w"/><span class="s">"text/event-stream; charset=utf-8"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseHeaders</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"Cache-Control"</span><span class="p">,</span><span class="w"/><span class="s">"no-cache"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseHeaders</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"Connection"</span><span class="p">,</span><span class="w"/><span class="s">"keep-alive"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">200</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseBody</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="n">OutputStreamWriter</span><span class="w"/><span class="n">osw</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">OutputStreamWriter</span><span class="p">(</span><span class="n">os</span><span class="p">,</span><span class="w"/><span class="n">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="n">BufferedWriter</span><span class="w"/><span class="n">bw</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">BufferedWriter</span><span class="p">(</span><span class="n">osw</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">          </span><span class="n">PrintWriter</span><span class="w"/><span class="n">out</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">PrintWriter</span><span class="p">(</span><span class="n">bw</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="kt">long</span><span class="w"/><span class="n">id</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">while</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">shutdownMessage</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">sendMessages</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">id</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">String</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"tick @"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">currentTimeMillis</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">writeEvent</span><span class="p">(</span><span class="n">out</span><span class="p">,</span><span class="w"/><span class="s">"tick"</span><span class="p">,</span><span class="w"/><span class="n">data</span><span class="p">,</span><span class="w"/><span class="n">Long</span><span class="p">.</span><span class="na">toString</span><span class="p">(</span><span class="n">id</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">heartbeat</span><span class="p">(</span><span class="n">out</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">1000</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="c1">// Optional: send farewell event before shutting down</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">writeEvent</span><span class="p">(</span><span class="n">out</span><span class="p">,</span><span class="w"/><span class="s">"shutdown"</span><span class="p">,</span><span class="w"/><span class="s">"Server is shutting down"</span><span class="p">,</span><span class="w"/><span class="n">Long</span><span class="p">.</span><span class="na">toString</span><span class="p">(</span><span class="o">++</span><span class="n">id</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="n">ioe</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">logger</span><span class="p">().</span><span class="na">warn</span><span class="p">(</span><span class="s">"SSE client disconnected: {}"</span><span class="p">,</span><span class="w"/><span class="n">ioe</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"SSE stream closed"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">server</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"SSE server running at http://localhost:{}/sse"</span><span class="p">,</span><span class="w"/><span class="n">port</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">Runtime</span><span class="p">.</span><span class="na">getRuntime</span><span class="p">().</span><span class="na">addShutdownHook</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Thread</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Stopping server …"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">server</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">server</span><span class="p">.</span><span class="na">stop</span><span class="p">(</span><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">cmdShutdown</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Shutdown command received – 'shutdown' event will be sent …"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">shutdownMessage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/><span class="c1">// signals all active handlers to send a shutdown event</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="c1">// Grace period so handlers can still execute writeEvent(..., "shutdown", ...) + flush</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">1200</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">InterruptedException</span><span class="w"/><span class="n">ie</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">interrupt</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">server</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">server</span><span class="p">.</span><span class="na">stop</span><span class="p">(</span><span class="n">0</span><span class="p">);</span><span class="w"/><span class="c1">// stop HTTP server afterwards</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">logger</span><span class="p">().</span><span class="na">warn</span><span class="p">(</span><span class="s">"Error while stopping: {}"</span><span class="p">,</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Application is shutting down."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">System</span><span class="p">.</span><span class="na">exit</span><span class="p">(</span><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">cmdStop</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">sendMessages</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"SSE message sending stopped via CLI"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">cmdStart</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">sendMessages</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"SSE message sending started via CLI"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// Helper function for sending an event in SSE format</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">writeEvent</span><span class="p">(</span><span class="n">PrintWriter</span><span class="w"/><span class="n">out</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">event</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">data</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">id</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">event</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">out</span><span class="p">.</span><span class="na">printf</span><span class="p">(</span><span class="s">"event: %s%n"</span><span class="p">,</span><span class="w"/><span class="n">event</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">id</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="o">!</span><span class="n">id</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">out</span><span class="p">.</span><span class="na">printf</span><span class="p">(</span><span class="s">"id: %s%n"</span><span class="p">,</span><span class="w"/><span class="n">id</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">data</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="c1">// Correctly output multiline data</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">line</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">data</span><span class="p">.</span><span class="na">split</span><span class="p">(</span><span class="s">"\\R"</span><span class="p">,</span><span class="w"/><span class="o">-</span><span class="n">1</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">out</span><span class="p">.</span><span class="na">printf</span><span class="p">(</span><span class="s">"data: %s%n"</span><span class="p">,</span><span class="w"/><span class="n">line</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">out</span><span class="p">.</span><span class="na">print</span><span class="p">(</span><span class="s">"\n"</span><span class="p">);</span><span class="w"/><span class="c1">// terminate message with empty line</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">out</span><span class="p">.</span><span class="na">flush</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="c1">// Comment-based heartbeat, ignored by client – keeps connection alive</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">heartbeat</span><span class="p">(</span><span class="n">PrintWriter</span><span class="w"/><span class="n">out</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">out</span><span class="p">.</span><span class="na">print</span><span class="p">(</span><span class="s">": keep-alive\n\n"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">out</span><span class="p">.</span><span class="na">flush</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>**Quick function test</p><ul><li>Start: java Application (or compile/execute via javac/java).</li><li>Check with a small Java program:</li></ul><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.URI</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.http.HttpClient</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.http.HttpRequest</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.http.HttpResponse</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">java.net.http.HttpClient.newHttpClient</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SseTestClient</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">// Create a new HTTP/2 client</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">HttpClient</span><span class="w"/><span class="n">client</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">newHttpClient</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">// Build GET request to connect to the SSE endpoint</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">HttpRequest</span><span class="w"/><span class="n">request</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">HttpRequest</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">uri</span><span class="p">(</span><span class="n">URI</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="s">"http://localhost:8080/sse"</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">GET</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">// Asynchronously send request and consume the response stream line by line</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">client</span><span class="p">.</span><span class="na">sendAsync</span><span class="p">(</span><span class="n">request</span><span class="p">,</span><span class="w"/><span class="n">HttpResponse</span><span class="p">.</span><span class="na">BodyHandlers</span><span class="p">.</span><span class="na">ofLines</span><span class="p">())</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">thenAccept</span><span class="p">(</span><span class="n">response</span><span class="w"/><span class="o">-&gt;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">response</span><span class="p">.</span><span class="na">body</span><span class="p">().</span><span class="na">forEach</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">::</span><span class="n">println</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">join</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This simple program opens a connection to the server and outputs the received SSE messages line by line to the console. Continuous event:/id:/data: blocks and : keep-alive comments are expected.</p><h2 id="chapter-4--minimal-sse-client-in-core-java">Chapter 4 – Minimal SSE Client in Core Java</h2><h3 id="41-implementation-with-httpclient">4.1 Implementation with<em>HttpClient</em></h3><p>On the client side, the JDK has offered a powerful API since Java 11 with java.net.http.HttpClient to process HTTP connections asynchronously. Because SSE is based on a persistent GET request, the client can continuously receive incoming data via HttpClient. In contrast to classic REST requests, which are completed after the body has been entirely accepted, the connection remains open and delivers lines in<em>text/event-stream</em> format.</p><p>We encapsulate the implementation in a dedicated class SseClient, which contains no static methods. The client is then started via a separate class SseClientApp.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.sse.client;</span></span><span class="line"><span class="cl">import com.svenruppert.dependencies.core.logger.HasLogger;</span></span><span class="line"><span class="cl">import java.io.IOException;</span></span><span class="line"><span class="cl">import java.net.URI;</span></span><span class="line"><span class="cl">import java.net.http.HttpClient;</span></span><span class="line"><span class="cl">import java.net.http.HttpRequest;</span></span><span class="line"><span class="cl">import java.net.http.HttpResponse;</span></span><span class="line"><span class="cl">import java.time.Duration;</span></span><span class="line"><span class="cl">import java.util.function.Consumer;</span></span><span class="line"><span class="cl">import java.util.stream.Stream;</span></span><span class="line"><span class="cl">/**</span></span><span class="line"><span class="cl"> * Minimal, instance-based SSE client built on java.net.http.HttpClient.</span></span><span class="line"><span class="cl"> * - manages Last-Event-ID</span></span><span class="line"><span class="cl"> * - implements a reconnect loop</span></span><span class="line"><span class="cl"> * - parses text/event-stream</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl">public final class SseClient</span></span><span class="line"><span class="cl">    implements HasLogger, AutoCloseable {</span></span><span class="line"><span class="cl">  private final HttpClient http;</span></span><span class="line"><span class="cl">  private final URI uri;</span></span><span class="line"><span class="cl">  private final Duration reconnectDelay = Duration.ofSeconds(2);</span></span><span class="line"><span class="cl">  private volatile boolean running = true;</span></span><span class="line"><span class="cl">  private volatile String lastEventId = null;</span></span><span class="line"><span class="cl">  public SseClient(URI uri) {</span></span><span class="line"><span class="cl">    this.uri = uri;</span></span><span class="line"><span class="cl">    this.http = HttpClient.newBuilder()</span></span><span class="line"><span class="cl">        .connectTimeout(Duration.ofSeconds(5))</span></span><span class="line"><span class="cl">        .build();</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  /**</span></span><span class="line"><span class="cl">   * Starts the streaming loop and invokes the callback for each complete event.</span></span><span class="line"><span class="cl">   */</span></span><span class="line"><span class="cl">  public void run(Consumer<span class="nt">&lt;SseEvent&gt;</span> onEvent) {</span></span><span class="line"><span class="cl">    while (running) {</span></span><span class="line"><span class="cl">      try {</span></span><span class="line"><span class="cl">        HttpRequest.Builder b = HttpRequest.newBuilder()</span></span><span class="line"><span class="cl">            .uri(uri)</span></span><span class="line"><span class="cl">            .timeout(Duration.ofMinutes(10))</span></span><span class="line"><span class="cl">            .GET();</span></span><span class="line"><span class="cl">        if (lastEventId != null) {</span></span><span class="line"><span class="cl">          b.setHeader("Last-Event-ID", lastEventId);</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        HttpRequest req = b.build();</span></span><span class="line"><span class="cl">        // synchronous streaming so we can parse line by line</span></span><span class="line"><span class="cl">        HttpResponse<span class="nt">&lt;Stream</span><span class="err">&lt;String</span><span class="nt">&gt;</span>&gt; resp =</span></span><span class="line"><span class="cl">            http.send(req, HttpResponse.BodyHandlers.ofLines());</span></span><span class="line"><span class="cl">        if (resp.statusCode() != 200) {</span></span><span class="line"><span class="cl">          logger().warn("Unexpected status {} from {}", resp.statusCode(), uri);</span></span><span class="line"><span class="cl">          sleep(reconnectDelay);</span></span><span class="line"><span class="cl">          continue;</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        try {</span></span><span class="line"><span class="cl">          parseStream(resp.body(), onEvent);</span></span><span class="line"><span class="cl">        } catch (java.io.UncheckedIOException uioe) {</span></span><span class="line"><span class="cl">          // Common case on server shutdown: stream is closed → reconnect gracefully</span></span><span class="line"><span class="cl">          var cause = uioe.getCause();</span></span><span class="line"><span class="cl">          logger().info("Stream closed ({}). Reconnecting …",</span></span><span class="line"><span class="cl">                        cause != null</span></span><span class="line"><span class="cl">                            ? cause.getClass().getSimpleName()</span></span><span class="line"><span class="cl">                            : uioe.getClass().getSimpleName());</span></span><span class="line"><span class="cl">        } catch (RuntimeException re) {</span></span><span class="line"><span class="cl">          logger().warn("Unexpected runtime exception in parser: {}", re.getMessage());</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">      } catch (IOException | InterruptedException e) {</span></span><span class="line"><span class="cl">        if (!running) break; // terminated normally</span></span><span class="line"><span class="cl">        logger().warn("Connection interrupted ({}). Reconnecting in {}s …",</span></span><span class="line"><span class="cl">                      e.getClass().getSimpleName(), reconnectDelay.toSeconds());</span></span><span class="line"><span class="cl">        sleep(reconnectDelay);</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  private void parseStream(Stream<span class="nt">&lt;String&gt;</span> lines, Consumer<span class="nt">&lt;SseEvent&gt;</span> onEvent) {</span></span><span class="line"><span class="cl">    String event = null, id = null;</span></span><span class="line"><span class="cl">    StringBuilder data = null;</span></span><span class="line"><span class="cl">    try {</span></span><span class="line"><span class="cl">      for (String line : (Iterable<span class="nt">&lt;String&gt;</span>) lines::iterator) {</span></span><span class="line"><span class="cl">        if (line.isEmpty()) {</span></span><span class="line"><span class="cl">          // finish current message</span></span><span class="line"><span class="cl">          if (data != null) {</span></span><span class="line"><span class="cl">            String payload = data.toString();</span></span><span class="line"><span class="cl">            SseEvent ev = new SseEvent(event, payload, id);</span></span><span class="line"><span class="cl">            if (id != null) lastEventId = id; // remember progress</span></span><span class="line"><span class="cl">            // shutdown signal from server → client terminates gracefully</span></span><span class="line"><span class="cl">            if ("shutdown".equalsIgnoreCase(ev.event())) {</span></span><span class="line"><span class="cl">              try {</span></span><span class="line"><span class="cl">                onEvent.accept(ev);</span></span><span class="line"><span class="cl">              } catch (RuntimeException ex) {</span></span><span class="line"><span class="cl">                logger().warn("Event callback threw exception: {}", ex.getMessage());</span></span><span class="line"><span class="cl">              }</span></span><span class="line"><span class="cl">              this.running = false;</span></span><span class="line"><span class="cl">              break; // leave parsing loop</span></span><span class="line"><span class="cl">            }</span></span><span class="line"><span class="cl">            try {</span></span><span class="line"><span class="cl">              onEvent.accept(ev);</span></span><span class="line"><span class="cl">            } catch (RuntimeException ex) {</span></span><span class="line"><span class="cl">              logger().warn("Event callback threw exception: {}", ex.getMessage());</span></span><span class="line"><span class="cl">            }</span></span><span class="line"><span class="cl">          }</span></span><span class="line"><span class="cl">          event = null;</span></span><span class="line"><span class="cl">          id = null;</span></span><span class="line"><span class="cl">          data = null;</span></span><span class="line"><span class="cl">          continue;</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        if (line.startsWith(":")) {</span></span><span class="line"><span class="cl">          // comment/heartbeat — silently ignore (avoid log spam)</span></span><span class="line"><span class="cl">          continue;</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        int idx = line.indexOf(':');</span></span><span class="line"><span class="cl">        String field = (idx &gt;= 0 ? line.substring(0, idx) : line).trim();</span></span><span class="line"><span class="cl">        String value = (idx &gt;= 0 ? line.substring(idx + 1) : "");</span></span><span class="line"><span class="cl">        if (value.startsWith(" ")) value = value.substring(1); // drop optional leading space</span></span><span class="line"><span class="cl">        switch (field) {</span></span><span class="line"><span class="cl">          case "event" -&gt; event = value;</span></span><span class="line"><span class="cl">          case "id" -&gt; id = value;</span></span><span class="line"><span class="cl">          case "data" -&gt; {</span></span><span class="line"><span class="cl">            if (data == null) data = new StringBuilder();</span></span><span class="line"><span class="cl">            if (!data.isEmpty()) data.append(' ');</span></span><span class="line"><span class="cl">            data.append(value);</span></span><span class="line"><span class="cl">          }</span></span><span class="line"><span class="cl">          default -&gt; { /* unknown field ignored */ }</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    } catch (java.io.UncheckedIOException uioe) {</span></span><span class="line"><span class="cl">      // Typically EOF/closed when server stops → do not throw further</span></span><span class="line"><span class="cl">      var cause = uioe.getCause();</span></span><span class="line"><span class="cl">      logger().info("Input stream ended ({}).",</span></span><span class="line"><span class="cl">                    cause != null</span></span><span class="line"><span class="cl">                        ? cause.getClass().getSimpleName()</span></span><span class="line"><span class="cl">                        : uioe.getClass().getSimpleName());</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  private void sleep(Duration d) {</span></span><span class="line"><span class="cl">    try {</span></span><span class="line"><span class="cl">      Thread.sleep(d.toMillis());</span></span><span class="line"><span class="cl">    } catch (InterruptedException ignored) {</span></span><span class="line"><span class="cl">      Thread.currentThread().interrupt();</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  @Override</span></span><span class="line"><span class="cl">  public void close() { running = false; }</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">package com.svenruppert.sse.client;</span></span><span class="line"><span class="cl">import com.svenruppert.dependencies.core.logger.HasLogger;</span></span><span class="line"><span class="cl">import java.net.URI;</span></span><span class="line"><span class="cl">public final class SseClientApp implements HasLogger {</span></span><span class="line"><span class="cl">  public static void main(String[] args) {</span></span><span class="line"><span class="cl">    String url = (args.length &gt; 0 ? args[0] : "http://localhost:8080/sse");</span></span><span class="line"><span class="cl">    URI uri = URI.create(url);</span></span><span class="line"><span class="cl">    try (SseClient client = new SseClient(uri)) {</span></span><span class="line"><span class="cl">      Runtime.getRuntime().addShutdownHook(new Thread(client::close));</span></span><span class="line"><span class="cl">      client.run(ev -&gt; {</span></span><span class="line"><span class="cl">        // You could branch based on ev.event(); for now we just log</span></span><span class="line"><span class="cl">        if (ev.id() != null) {</span></span><span class="line"><span class="cl">          System.out.printf("[%s] id=%s data=%s%n", ev.event(), ev.id(), ev.data());</span></span><span class="line"><span class="cl">        } else {</span></span><span class="line"><span class="cl">          System.out.printf("[%s] data=%s%n", ev.event(), ev.data());</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">      });</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h3 id="42-interpreting-events">4.2 Interpreting Events</h3><p>The SseClient has a parseEvents(Stream<String>) method that parses incoming rows for event:, data:, and id: fields and returns them as SseEvent objects. This allows the received data to be structured and processed in a targeted manner.</p><h3 id="43-behaviour-in-case-of-aborting-and-reconnect">4.3 Behaviour in case of aborting and reconnect</h3><p>The infinite loop mechanism implemented in the start() method ensures that the client automatically establishes a new connection in the event of an abort. A short break is taken between reconnects to avoid endless loops in the event of permanent connection problems.</p><p>With this architecture, the client is clearly structured: SseClient encapsulates the logic, while SseClientApp is the starting point for execution. This means that the implementation remains modular, testable and platform-independent.</p><p><strong>Hints</strong></p><ul><li>SseClient has no static methods; it is controlled by instance state (running, lastEventId).</li><li>SseClientApp is the entry point. The URL can be passed as an argument; Standard is http://localhost:8080/sse.</li><li>Reconnect takes place automatically with the last received Last Event ID.</li><li>The callback interface (Consumer<SseEvent>) enables flexible further processing (logging, UI updates, persistence, etc.).</li></ul><h2 id="chapter-5--testing-and-debugging-sse">Chapter 5 – Testing and Debugging SSE</h2><h3 id="51-using-curl-and-browser-eventsource">5.1 Using curl and browser EventSource</h3><p>To check SSE endpoints, developers often turn to<em>curl</em> because it&rsquo;s available on almost all platforms. With the -N option (no buffering) the messages can be made directly visible:</p><p>curl -N http://localhost:8080/sse</p><p>The received events appear in the terminal in their raw form, including event:, data: and id: fields. An alternative is to use the JavaScript API EventSource in the browser:</p><p>const source = new EventSource(&ldquo;http://localhost:8080/sse&rdquo;);</p><p>source.onmessage = e = &gt; console.log(e.data);</p><p>This allows the behavior of the server to be tracked directly in the browser.</p><h3 id="52-checking-with-java-client">5.2 Checking with Java Client</h3><p>Since this article deliberately emphasizes platform independence, a separate Java client is a good idea instead of external tools (see chapter 4). This can be used both as a permanent listener and specifically for test cases, for example by consuming only a certain number of events and then terminating them.</p><p>Example: A test run that collects exactly five events and then ends:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">SseClient client = new SseClient(URI.create("http://localhost:8080/sse"));</span></span><span class="line"><span class="cl">List<span class="nt">&lt;SseEvent&gt;</span> events = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">client.run(ev -&gt; {</span></span><span class="line"><span class="cl">  events.add(ev);</span></span><span class="line"><span class="cl">  if (events.size() &gt;= 5) {</span></span><span class="line"><span class="cl">    client.close();</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">});</span></span></code></pre></div></div><p>This allows functional and integration tests to be carried out directly in the Java environment without dependence on curl or telnet .</p><h3 id="53-monitoring-and-logging">5.3 Monitoring and logging</h3><p>Precise monitoring of SSE connections is indispensable for the operation of productive systems. Monitoring is the systematic recording of key figures such as the number of active clients or the average duration of a connection. Equally important is end-to-end error tracking, which documents occurring IO errors, timeouts and aborted transmissions and thus forms the basis for rapid troubleshooting. In addition, continuous monitoring of the heartbeats ensures that comments or keep-alive signals are sent at regular intervals, keeping the connections stable.</p><p>Effective logging supports this monitoring and should be done on both the server and client sides. This makes it possible to isolate network problems or interference caused by proxies more quickly. It is beneficial to record unique identifiers for connections and individual events in the log to make communication paths transparent and comprehensible even in complex scenarios.</p><p>These tools can be used to reliably track, reproduce and monitor the behaviour of SSE implementations in production environments.</p><h2 id="chapter-6--application-scenarios-and-limitations">Chapter 6 – Application Scenarios and Limitations</h2><p>Server-sent events (SSE) find their strength in fields of application where continuous, unidirectional data transmission from the server to the client is required. It is particularly obvious to use it in the field of notification systems. Here, events such as the arrival of new messages, status changes in a workflow or system warnings can be transmitted directly to users without them having to make repeated requests actively. Another typical scenario is monitoring solutions, in which measured values or system metrics are visualised in real time. SSE also offers a resource-saving option for continuous data delivery in telemetry, for example, in the transmission of sensor data from distributed systems.</p><p>At the same time, it is essential to consider the limitations of the procedure. Since SSE only transmits UTF-8 encoded text data, it is not possible to transmit binary content directly. Although binary data can be encoded in text form (e.g. using Base64), this is at the expense of efficiency. SSE is also limited to one-way communication: the server can send data to the client, but not vice versa. For scenarios that require bidirectional exchange, WebSockets or similar technologies are more suitable. Network Restrictive problem infrastructures represent another problem area. Proxies or firewalls can block or disconnect long-term open HTTP connections after a certain amount of time, necessitating an additional reconnect strategy.</p><p>Safety aspects should also not be neglected when using SSE. Since the connection is maintained via the HTTP protocol, particular attention must be paid to transport encryption using HTTPS. In addition, mechanisms should be implemented to prevent misuse by unauthorised clients, such as authentication and access control to the SSE endpoint. After all, resource conservation is a central issue: While SSE works much more efficiently than polling, the simultaneous supply of a massive number of clients can lead to a high load on server resources. Scaling strategies such as load balancing or splitting clients across multiple SSE endpoints can help here.</p><p>In addition, it makes sense to name the functions provided in the specification in detail. The message structure provides several possible fields: event: determines the type of the message and allows differentiated event handlers in the client; data: contains the actual message body and can be continued across multiple lines, with the client automatically combining the contents into a data block; id: allows each message to be uniquely labeled so that the Last-Event-ID header can be seamlessly continued when the connection is re-established; retry: signals to the client after which time an automatic connection should be established in case the connection is interrupted; finally, comments can be inserted via a leading colon: which are ignored by the client but used for keep-alive mechanisms. These fields together form the normative framework that every compliant SSE server should meet.</p><p>Overall, SSE is suitable for a wide range of real-time scenarios, as long as the inherent limitations are taken into account and compensated for by appropriate architectural decisions. The technology offers an elegant solution for cases where simplicity, standards compliance, and stable, server-side push communication are paramount.</p>
]]></content:encoded><category>Java</category><category>Serverside</category><media:content url="https://svenruppert.com/images/2025/09/ChatGPT-Image-4.-Sept.-2025-18_02_53.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/09/ChatGPT-Image-4.-Sept.-2025-18_02_53.jpeg"/><enclosure url="https://svenruppert.com/images/2025/09/ChatGPT-Image-4.-Sept.-2025-18_02_53.jpeg" type="image/jpeg" length="0"/></item><item><title>Core Java - Flow.Processor</title><link>https://svenruppert.com/posts/core-java-flow-processor/</link><pubDate>Thu, 04 Sep 2025 11:27:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/core-java-flow-processor/</guid><description>Reactive streams address a fundamental problem of modern systems: Producers (sensors, services, user events) deliver data at an unpredictable rate , while consumers (persistence, UI, analytics) can only process data at a limited speed. Without a flow control model , backlogs, storage pressure, and ultimately outages occur.</description><content:encoded>&lt;![CDATA[<p>Reactive streams address a fundamental problem of modern systems:<strong>Producers</strong> (sensors, services, user events) deliver data at<em>an unpredictable rate</em> , while<strong>consumers</strong> (persistence, UI, analytics) can only process data at a limited speed. Without a<em>flow control model</em> , backlogs, storage pressure, and ultimately outages occur.</p><p>With Java, java.util.concurrent.Flow provides a minimalist API that standardises this problem:<strong>Publisher → Processor → Subscriber,</strong> including<strong>backpressure</strong>. Flow.Processor&lt;I,O&gt; is the<strong>hinge</strong> between upstream and downstream:<em>process, transform, buffer, throttle</em> , and at the same time correctly respect the demand.</p><p><strong>Abstract:</strong> Reactive Streams =<em>asynchronous push model with backpressure</em>. Java Streams =<em>synchronous PullModel without backpressure</em>.</p><ol><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#1-1-why-reactive-streams">1.1 Why Reactive Streams?</a><ol><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#where-does-that-fit-in-the-jdk">Where does that fit in the JDK?</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#1-2-push-vs-pull-reactive-streams-vs-java-streams">1.2 Push vs. Pull: Reactive Streams vs. Java Streams</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#1-3-typical-use-cases">1.3 Typical Use Cases</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#2-overview-java-util-concurrent-flow">2. Overview: java.util.concurrent.Flow</a><ol><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#2-1-life-cycle-of-the-signals">2.1 Life cycle of the signals</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#2-2-backpressure-basic-principle">2.2 Backpressure basic principle</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#3-focus-on-flow-processor-i-o">3. Focus on Flow.Processor&lt;I,O&gt;</a><ol><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#3-1-contract-responsibilities">3.1 Contract &amp; Responsibilities</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#type-variables-invariants">Type Variables &amp; Invariants</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#chain-position-between-upstream-downstream">Chain position: between upstream &amp; downstream</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#4-practical-introduction-with-the-jdk">4. Practical introduction with the JDK</a><ol><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#4-1-submissionpublisher-in-a-nutshell">4.1 SubmissionPublisher in a nutshell</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#4-2-simple-pipeline-without-its-own-processor">4.2 Simple pipeline without its own processor</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#4-3-limitations-of-submissionpublisher">4.3 Limitations of SubmissionPublisher</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#5-sample-processor-with-code-examples">5. Sample Processor with Code Examples</a><ol><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#5-1-mapprocessor-i-o-transformation">5.1 MapProcessor&lt;I,O&gt; Transformation</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#5-2-filterprocessor-t-filtering-data">5.2 FilterProcessor<T> – Filtering Data</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#5-3-batchingprocessor-t-collect-and-share">5.3 BatchingProcessor<T> – Collect and Share</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#5-4-throttleprocessor-t-throttling">5.4 ThrottleProcessor<T> Throttling</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#6-backpressure-in-practice">6. Backpressure in practice</a><ol><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#6-1-strategies-for-dealing-with-backpressure">6.1 Strategies for dealing with backpressure</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#6-2-demand-policy-batch-vs-single-retrieval">6.2 Demand Policy: Batch vs. Single Retrieval</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#6-3-monitoring-tuning-and-capacity-planning">6.3 Monitoring, Tuning and Capacity Planning</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#concurrency-execution-context">Concurrency &amp; Execution Context</a><ol><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#7-1-executor-strategies">7.1 Executor Strategies</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#7-2-reentrancy-traps-and-serialization-of-signals">7.2 Reentrancy traps and serialization of signals</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#7-3-virtual-threads-loom-vs-reactive">7.3 Virtual Threads (Loom) vs. Reactive</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#error-completion-scenarios">Error &amp; Completion Scenarios</a><ol><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#8-1-semantics-of-onerror-and-oncomplete">8.1 Semantics of onError and onComplete</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#8-2-restarts-and-retry-mechanisms">8.2 Restarts and Retry Mechanisms</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#8-3-idem-potency-and-exactly-once-processing">8.3 Idem potency and exactly-once processing</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#interoperability-integration">Interoperability &amp; Integration</a><ol><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#9-1-integration-with-reactive-frameworks">9.1 Integration with Reactive Frameworks</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#9-2-use-in-classic-applications">9.2 Use in classic applications</a></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#9-3-bridge-to-java-streams">9.3 Bridge to Java Streams</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/core-java-flow-processor/#result">Result</a></li></ol><h2 id="11-why-reactive-streams">1.1 Why Reactive Streams?</h2><ul><li><strong>Controlled load (backpressure):</strong> Consumers actively signal how many items they can process (request(s)) by requesting the exact number of items that they can currently process safely via their subscription. The publisher may only deliver as many items as previously requested, maintaining a controlled processing speed, ensuring buffers do not overflow, and keeping memory consumption predictable. The demand can be adjusted dynamically (e.g. batch request(32) or item-wise request(1)). In case of overload, the consumer can pause or cancel cleanly by calling<strong>cancel().</strong></li><li><strong>Asynchrony &amp; Decoupling:</strong> Data flows can cross thread and service boundaries. This means that a publisher is operated in its own thread or via an executor, for example, while the subscriber processes in a different context. In between, there can be queues or network boundaries, for example, when events are sent from one microservice to another. Loose coupling allows systems to scale and distribute without processing being tightly tied to a single execution context.</li><li><strong>Infinite/long flows:</strong> Telemetry, logs, and EventStreams are often continuous, potentially infinite data sources. A single pull, as is standard in classic Java streams, is insufficient here because the data is not available at a fixed time, but rather constantly accumulates. Instead, you need a permanent subscription that continuously informs the consumer about new events. This is the only way to process continuous data streams without the consumer having to poll or repeatedly start new streams actively.</li><li><strong>Error semantics &amp; completion:</strong> Consistent signals such as onError and onComplete ensure that each data pipeline receives a well-defined closure or transparent error handling. Instead of unpredictable exceptions that occur somewhere in the code, there are standardised callback methods that clearly mark the lifetime of a stream. This enables the reliable distinction between a data stream that has been terminated regularly and one that has encountered an error, allowing downstream components to react accordingly, such as through cleanup, retry mechanisms, or logging. This predictability is crucial for robust pipelines that are to run stably over more extended periods of time.</li><li><strong>Composability:</strong> Composability plays a significant role: A processor can be used as a transformation, for example, by modifying incoming data according to specific rules – similar to a map or filter operation in Java streams. In addition, control operators can be implemented, such as a throttle that limits the rate or a debounce, which only passes on the last signal of a short period of time. Finally, a processor also enables the construction of more complex topologies, such as FanIn, where several sources are merged, or FanOut, where a data stream is distributed among multiple subscribers. This allows entire processing pipelines to be assembled in a modular manner without the individual components having to know their internal logic.</li><li>**Operation &amp; Security: **Backpressure is not only used for load control, but also acts as effective protection against denial‑of service attacks. A system that writes data to infinite buffers without restraint can easily be paralysed by excessive input. With correctly implemented backpressure, on the other hand, every demand is strictly enforced: The publisher only delivers as many elements as were actually requested. This prevents malicious or faulty sources from overloading the system by producing data en masse. Instead, resource consumption remains predictable and controlled, increasing the stability and security of the entire pipeline.</li></ul><h3 id="where-does-that-fit-in-the-jdk">Where does that fit in the JDK?</h3><ul><li><strong>Namespace</strong> : java.util.concurrent.Flow – this package bundles the core building blocks of the reactive API. It provides interfaces for publishers, subscribers, subscriptions, and processors, and thus forms the standard framework for reactive streams in the JDK. This ensures that all implementations adhere to the same contracts and can be seamlessly integrated.</li><li><strong>Roles</strong> : The roles can be distinguished as follows: A publisher creates and delivers data to its subscribers, a subscriber consumes this data and processes it further, the subscription forms the contract between the two and regulates the backpressure signals, among other things, and the processor takes on a dual role by receiving data as a subscriber and at the same time passing on transformed or filtered data to the next consumer as a publisher.</li><li>The<strong>types</strong> in the Flow API are generic and require careful and thread-safe handling. Generic means that Publisher, Subscriber, and Processor are always instantiated with specific type variables, so that the data type remains consistent throughout the pipeline. At the same time, strict attention must be paid to thread safety during implementation, as signals such as onNext or onError can arrive from different threads and must be synchronised correctly. As an introduction, the JDK provides a reference implementation with the SubmissionPublisher, which is sufficient for simple scenarios and already takes into account essential concepts such as backpressure. In production systems, however, developers often fall back on their own or adapted processor classes to precisely implement specific requirements for buffering, transformation, error handling or performance.</li></ul><h3 id="12-push-vs-pull-reactive-streams-vs-java-streams">1.2 Push vs. Pull: Reactive Streams vs. Java Streams</h3><table><thead><tr><th><strong>Aspect</strong></th><th><strong>Java Streams (Pull)</strong></th><th><strong>Reactive Streams / Flow (Push)</strong></th></tr></thead><tbody><tr><td>**Data direction</td><td>Consumer pulls data</td><td>Producer pushes data</td></tr><tr><td><strong>Execution</strong></td><td>type. synchronously in the calling thread</td><td>asynchronous, often via executor/threads</td></tr><tr><td>**Backpressure</td><td>non-existent</td><td><strong>central concept</strong> (request(s))</td></tr><tr><td><strong>Lifetime</strong></td><td>finite, terminal operation</td><td>Potentially infinite, subscription-based</td></tr><tr><td>**Error</td><td>Exceptions in the caller</td><td>onError‑signal in the stream</td></tr><tr><td><strong>Demolition</strong></td><td>End of Source</td><td>cancel() of the subscription</td></tr></tbody></table><p><strong>Minimal examples</strong></p><p><em>Pull (Java Streams)</em> – the consumer sets the pace:</p><p>Note: SubmissionPublisher is a convenient way to get started, but not a panacea (limited tuning options). Custom processor‑implementations give you full control over backpressure, buffer, and concurrency.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Test</span></span><span class="line"><span class="cl">  void test001() {</span></span><span class="line"><span class="cl">    var data = List.of(1, 2, 3, 4, 5);</span></span><span class="line"><span class="cl">    int sum = data.stream()</span></span><span class="line"><span class="cl">        .filter(n -&gt; n % 2 == 1)</span></span><span class="line"><span class="cl">        .mapToInt(n -&gt; n * n)</span></span><span class="line"><span class="cl">        .sum();</span></span><span class="line"><span class="cl">    System.out.println("sum = " + sum);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//Push (flow) – the producer delivers, the subscriber signals demand:</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">import java.util.concurrent.Flow.*;</span></span><span class="line"><span class="cl">import java.util.concurrent.SubmissionPublisher;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  @Test</span></span><span class="line"><span class="cl">  void test002() {</span></span><span class="line"><span class="cl">    class SumSubscriber implements Flow.Subscriber<span class="nt">&lt;Integer&gt;</span> {</span></span><span class="line"><span class="cl">      private Flow.Subscriptions;</span></span><span class="line"><span class="cl">      private int sum = 0;</span></span><span class="line"><span class="cl">      @Override public void onSubscribe(Flow.Subscription s) { this.s = s; s.request(1); }</span></span><span class="line"><span class="cl">      @Override public void onNext(Integer item) { sum += item; s.request(1); }</span></span><span class="line"><span class="cl">      @Override public void onError(Throwable t) { t.printStackTrace(); }</span></span><span class="line"><span class="cl">      @Override public void onComplete() { System.out.println("sum = " + sum); }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    try (var pub = new SubmissionPublisher<span class="nt">&lt;Integer&gt;</span>()) {</span></span><span class="line"><span class="cl">      pub.subscribe(new SumSubscriber());</span></span><span class="line"><span class="cl">      for (int i = 1; i<span class="err">&lt;</span>= 5; i++) pub.submit(i); asynchronous push</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span></code></pre></div></div><h3 id="13-typical-use-cases">1.3 Typical Use Cases</h3><ul><li><strong>Telemetry &amp; Logging:</strong> In modern systems, events are continuously created, such as metrics or log entries. These streams can be so high frequency that they cannot be permanently stored or transmitted in their raw form. This is where a processor comes into play, which first caches the events and then combines them in batches, for example, 100 events each. In this way, the flood of data can be effectively throttled and persisted in manageable portions, preventing overloading of individual components.</li><li><strong>Message ingestion:</strong> In many architectures, message and event systems, such as Kafka, classic message queues, or HTTP endpoints, form the input layer for data. These can be modelled as publishers who continuously provide new events. A processor then takes over the validation and possible enrichment, such as checking mandatory fields, adding additional metadata or transforming them into a uniform format. Only then does the enriched and checked data pass on to the subscriber, who takes over the actual persistence, for example, by writing it to a database or data warehouse. In this way, a clear separation of responsibility is achieved, allowing errors to be detected and addressed early in the data stream.</li><li><strong>IoT/streams from sensors:</strong> In practice, sensors often deliver data streams at highly fluctuating speeds. Some sensors transmit data in millisecond intervals, while others transmit data only every few seconds or minutes. If these flows are passed on unchecked to a central consumer, there is a risk of buffer overflow and thus instability in the overall system. With backpressure, however, the consumer can actively control how many measured values they take. In scenarios where the rate exceeds the processing capacity, additional strategies can be employed: either older values are discarded (<strong>DropStrategy</strong>) or only the most recent value is passed on (<strong>LatestStrategy</strong>). This keeps the system stable, allowing the consumer to always work with up-to-date and relevant data without being overwhelmed by data avalanches.</li><li><strong>UIEvents &amp; RateControl:</strong> In user interfaces, event streams with very high frequencies quickly arise, for example, when a user types a search query and an event is triggered for every keystroke. Without regulation, these events would be passed on to the<strong>BackendService</strong> unhindered, which could overload both the network connections and the servers. In this case, a special DebounceProcessor ensures that only the last event is forwarded within a short period of time. This avoids unnecessary inquiries, and the user still receives up-to-date suggestions promptly. This technique effectively prevents the UI from being flooded with data and, at the same time improves the perceived performance and responsiveness of the application.</li><li><strong>Security:</strong> For safety-critical applications, it is imperative that each stage of a data pipeline is clearly delineated and only communicates via defined contracts. This prevents the creation of an unbridled shared state that could lead to inconsistencies or security gaps. Another aspect is the possibility of interrupting data flows cleanly at any time. A cancel() method allows a subscriber to terminate the subscription if they determine that the source is faulty, provides too much data, or presents a potential security threat. In this way, resources can be freed up immediately, and the pipeline remains stable and resilient even under adverse conditions.</li></ul><h2 id="2-overview-javautilconcurrentflow">2. Overview: java.util.concurrent.Flow</h2><p>After explaining the motivation and the basic differentiation from Java Streams in the first chapter, we will now deal with the actual framework that the JDK provides for reactive streams. With the java.util.concurrent.The Flow package, a small but powerful API, has been provided since Java 9, describing the essential roles and contracts of Reactive Streams. These interfaces are deliberately kept minimal, allowing them to be used directly for simple use cases as well as forming the basis for more complex libraries.</p><p>The focus is on four roles that define the entire lifecycle of publishers and subscribers:<strong>Publisher</strong> ,<strong>Subscriber</strong> ,<strong>Subscription</strong> and<strong>Processor</strong>.</p><p>A<strong>publisher</strong> is the source of the data. He produces elements and makes them accessible to his subscribers. He may only send data if it has been explicitly requested beforehand. This prevents him from sending events uncontrollably, which in the worst case, fizzle out into nowhere or cause memory problems.</p><p>A<strong>subscriber</strong> is the counterpart to this: they receive the data that the publisher provides and process it further. To avoid being overloaded, the subscriber informs the publisher of the number of items they can include at a given time as part of the subscription.</p><p>The<strong>subscription</strong> is the link between the publisher and the subscriber. It is created at the moment of registration (the so-called subscribe process) and handed over to the subscriber. From then on, it controls the flow of data by providing methods such as request(long n) and cancel(). This controls demand and, simultaneously, enables a clean interruption of data transmission.</p><p>Finally, a<strong>processor</strong> combines both roles: it is a subscriber itself because it receives data from a publisher, but at the same time, it is also a publisher because it passes on the transformed or filtered data. This dual function is the great strength of the processor: it enables the construction of complex pipelines from simple building blocks, where transformation, filtering, or aggregation are clearly separated and encapsulated.</p><h3 id="21-life-cycle-of-the-signals">2.1 Life cycle of the signals</h3><p>The flow within the Flow API follows a clear order. As soon as a subscriber logs in to a publisher, the publisher first calls the subscriber&rsquo;s onSubscribe method and passes the subscription. Only when the subscriber requests elements via this subscription – such as request(1) or request(10) – does the publisher begin to deliver data. Each delivered item is delivered by a call to onNext.</p><p>If all data has been sent or the stream is terminated for other reasons, the publisher signals the end with onComplete. If, on the other hand, an error occurs, it is reported via onError . This precise signal semantics ensures that every data flow is either completed regularly or terminated with an error message. A subscriber, therefore, always knows exactly what state the data source is in and can react accordingly.</p><h3 id="22-backpressure-basic-principle">2.2 Backpressure basic principle</h3><p>A central concept of Flow is the so-called backpressure. It prevents publishers from flooding their subscribers with data. Instead, the subscribers themselves control how many items they want to retrieve. For example, a subscriber can initially request only a single element with request(1) and only ask for the next one after it has been processed. Similarly, it is possible to order larger quantities, such as request(50), to increase throughput. This model gives consumers complete control and ensures a stable balance between production and consumption.</p><p>Backpressure is not only a technical detail, but a decisive criterion for the robustness of reactive systems. Without this control, publishers could deliver events unchecked, overloading resources such as CPU or memory. Backpressure, on the other hand, can also be used to implement complex scenarios such as batching, prioritisation or flow control across thread and system boundaries.</p><p>The Flow API in the JDK defines the elementary building blocks of a reactive pipeline. Publisher, subscriber, subscription and processor together form a clear model that supports both small experiments and upscaled systems. The signal lifecycle and backpressure mechanisms ensure that the data streams are controlled, traceable and stable. This lays the foundation for delving deeper into the role of the processor in the following chapters and shedding light on its concrete applications.</p><h2 id="3-focus-on-flowprocessor-io">3. Focus on Flow.Processor &lt;I,O&gt;</h2><p>After the four roles were briefly described in the previous chapter, the focus now shifts to the processor. It occupies a key position in the flow API because it fulfils two roles simultaneously: it is<strong>a subscriber</strong> to its upstream, i.e., it receives data from a publisher, and it is also<strong>a publisher</strong> to its downstream, i.e., it passes on transformed or filtered data to other subscribers. This makes it act like a hinge in a chain of processing steps.</p><p>A processor is always provided with two type parameters: &lt;I, O&gt;. The first type describes which elements it receives from the upstream, and the second type describes which elements it passes on to the downstream. This allows any transformations to be mapped – for example, from raw sensor data to validated measured values or from text messages to structured objects.</p><h3 id="31-contract--responsibilities">3.1 Contract &amp; Responsibilities</h3><p>Implementing a processor means that you must abide by the rules of both subscribers and publishers. This includes, in particular, the following aspects:</p><ul><li><strong>Pay attention to signal flow:</strong> The processor must handle all signals it receives from the upstream device wholly and correctly. These include the onSubscribe, onNext, onError, and onComplete methods. Each of these signals fulfils a particular function in the lifecycle of a data stream: onSubscribe initiates the subscription and hands over control of the data flow, onNext signals the actual data, onError signals the occurrence of an error, and onComplete marks the regular end of the stream. It is essential that the processor strictly adheres to these semantics: An error may only be reported once and unambiguously, and no further data may follow after the onComplete event occurs. Only through these clear rules does processing remain consistent, predictable and reliable for all components involved.</li><li><strong>Respect backpressure:</strong> As a subscriber, the processor is obligated to adhere strictly to the rules of the backpressure model. This means that it may only receive and process exactly as many elements as it has previously actively requested from the upstream via its subscription – for example, by making a call such as request(1) or request(10). This prevents him from being overrun by a publisher who is too fast. At the same time, as a publisher, he has the responsibility to pass on this logic unchanged to his downstream. He is therefore not allowed to forward more elements to his subscribers than they have explicitly requested. The processor thus acts as a pass-through for the demand signals, ensuring that the entire chain, from publisher to processor to subscriber, remains in balance and that there are no overloads or data losses.</li><li><strong>Manage resources cleanly:</strong> A processor must also ensure that the resources it manages can be released cleanly at all times. In particular, this includes the fact that subscriptions can be actively terminated, for example, by calling cancel() if a subscriber loses interest or processing is to be interrupted for other reasons. It is equally vital that there are no memory or thread leaks: Open queues must be emptied or closed, background threads must be properly terminated, and executor services must be shut down again. Only through this consistent management can the system remain stable, performant and free of creeping resource damage in the long term.</li><li><strong>Do not swallow errors:</strong> If an error occurs, it must be consistently and transparently passed on to the downstream system via the onError method. This ensures that downstream subscribers are also clearly informed about the status of the data stream and can react – for example, through logging, retry mechanisms or the targeted termination of their own processing. If, on the other hand, an error is tacitly ignored, there is a risk of inconsistencies that are difficult to understand. Equally problematic is the uncontrolled throwing of unchecked exceptions, as they are not caught cleanly in the context of reactive pipelines; thus, entire processing chains can enter an undefined state. Clean error propagation is therefore a central quality feature of every processor implementation.</li></ul><h3 id="type-variables--invariants">Type Variables &amp; Invariants</h3><p>The type parameters can be used to determine precisely what kind of data a processor can process. For example, a processor&lt;string, integer&gt; could receive lines of text and extract numbers from them, which it then passes on. These types must be strictly adhered to a subscriber who expects integers must not receive strings unexpectedly. The genericity in the Flow API ensures that errors of this kind are already noticeable during compilation.</p><p>Additionally, the invariant holds that a processor must behave like both a correct subscriber<strong>and</strong> a correct publisher. He is therefore not only responsible for the transformation, but also for the proper transfer of tax information, such as demand and cancellation.</p><h3 id="chain-position-between-upstream--downstream">Chain position: between upstream &amp; downstream</h3><p>In practice, a processor is rarely used in isolation, but as an intermediate link in a chain. An upstream publisher delivers data that the processor receives, transforms or filters and then forwards to downstream subscribers. This creates flexible pipelines that can be expanded or exchanged depending on requirements.</p><p>For example, a logging processor can be placed between a data source and the actual consumer, also to record all elements. A processor can also be used to buffer data before it is passed on in larger batches. The position in the middle allows different aspects, such as transformation, control and observation, to be separated from each other and made modular.</p><p>The Flow.Processor is the link in reactive pipelines. Through his dual function as subscriber and publisher, he takes responsibility for the correct processing of signals, the implementation of transformations and compliance with the backpressure rules. Its type parameters &lt;I,O&gt; also ensure that data flows remain strictly typed and thus reliable. In the following chapters, we will show how to implement your own processor classes and which patterns have proven themselves in practice.</p><h2 id="4-practical-introduction-with-the-jdk">4. Practical introduction with the JDK</h2><p>Now that the concepts and contracts around the Flow.Processor have been presented in detail, it is time to start with a practical implementation in the JDK. Fortunately, Java has provided a reference implementation for a publisher since version 9, with the SubmissionPublisher, which is ideal for initial experiments. It relieves you of many details, such as thread management and internal buffering, allowing you to concentrate fully on the data flow.</p><p>The SubmissionPublisher is designed to distribute data asynchronously to its subscribers. In the background, it works with an executor that packages and distributes the individual signals, such as onNext, onError and onComplete, into separate tasks. By default, it uses the ForkJoinPool.commonPool(), but it can also pass its own executor. For small experiments, the standard configuration is usually sufficient; however, in production scenarios, it is worthwhile to adapt the executor and buffer sizes to your specific requirements.</p><h3 id="41-submissionpublisher-in-a-nutshell">4.1 SubmissionPublisher in a nutshell</h3><p>The SubmissionPublisher<T> class implements the Publisher<T> interface and is therefore an immediately usable source of data. It provides the submit(T item) method, which is used to add new items to the stream. These items are then automatically distributed to all registered subscribers. In this way, the publisher assumes the role of an asynchronous dispatcher, passing incoming data to any number of subscribers.</p><p>An essential detail is that the submit method does not block, but buffers the elements internally and then distributes them. However, if the buffer limits are reached, submit can block or even throw an IllegalStateException if the system is under too much load. That&rsquo;s why it&rsquo;s crucial to find the right balance between publisher speed, buffer size, and subscriber demand.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.util.concurrent.Flow.*;</span></span><span class="line"><span class="cl">import java.util.concurrent.SubmissionPublisher;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  @Test</span></span><span class="line"><span class="cl">  void test003() {</span></span><span class="line"><span class="cl">    class PrintSubscriber</span></span><span class="line"><span class="cl">        implements Flow.Subscriber<span class="nt">&lt;Integer&gt;</span> {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">      private Flow.Subscription subscription;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">      @Override</span></span><span class="line"><span class="cl">      public void onSubscribe(Flow.Subscription subscription) {</span></span><span class="line"><span class="cl">        System.out.println("Register: 1 element/req" );</span></span><span class="line"><span class="cl">        this.subscription = subscription;</span></span><span class="line"><span class="cl">        subscription.request(1); Request first element</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">      @Override</span></span><span class="line"><span class="cl">      public void onNext(Integer item) {</span></span><span class="line"><span class="cl">        System.out.println("Receive: " + item);</span></span><span class="line"><span class="cl">        subscription.request(1); request another after each item</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">      @Override</span></span><span class="line"><span class="cl">      public void onError(Throwable t) {</span></span><span class="line"><span class="cl">        t.printStackTrace();</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">      @Override</span></span><span class="line"><span class="cl">      public void onComplete() {</span></span><span class="line"><span class="cl">        System.out.println("Done!");</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    try (SubmissionPublisher<span class="nt">&lt;Integer&gt;</span> publisher = new SubmissionPublisher<span class="err">&lt;</span>&gt;()) {</span></span><span class="line"><span class="cl">      publisher.subscribe(new PrintSubscriber());</span></span><span class="line"><span class="cl">      for (int i = 1; i<span class="err">&lt;</span>= 5; i++) {</span></span><span class="line"><span class="cl">        System.out.println(" send element = " + i );</span></span><span class="line"><span class="cl">        publisher.submit(i);</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span></code></pre></div></div><h3 id="42-simple-pipeline-without-its-own-processor">4.2 Simple pipeline without its own processor</h3><p>To understand how SubmissionPublisher works, it&rsquo;s worth taking a simple example. This creates a publisher that sends numbers from 1 to 5 to a subscriber. The subscriber receives the data and outputs it to the console. This example shows the basic flow of onSubscribe, onNext, and onComplete.</p><p>In this program, the publisher asynchronously generates five values, which are transmitted to the subscriber one after the other. The subscriber explicitly requests an additional element each time, creating a controlled and stable flow of data. Finally, the publisher signals with onComplete that no more data will follow.</p><h3 id="43-limitations-of-submissionpublisher">4.3 Limitations of SubmissionPublisher</h3><p>Even though the SubmissionPublisher is very helpful for getting started, it quickly reaches its limits in more complex scenarios. For example, it offers only limited possibilities for configuring the backpressure behaviour. While the buffer size is customizable, it doesn&rsquo;t directly support more complex strategies, such as dropping or latest-value. Additionally, the standard executor is not suitable for all applications, particularly when high latency requirements or strict thread isolation are necessary.</p><p>Another point is that SubmissionPublisher is primarily intended for learning purposes and simple applications. In productive systems, people usually rely on their own publisher and processor implementations or on established reactive frameworks such as Project Reactor or RxJava, which are based on the Flow API and provide additional operators.</p><p>The SubmissionPublisher is a handy place to start to see the concepts of the Flow API in action. It shows how publishers and subscribers interact, how backpressure works and how signals are processed. At the same time, however, it also becomes clear that for more complex or productive scenarios, in-house processor implementations or external libraries are indispensable. In this way, he forms the bridge between the theoretical foundations and the first practical steps in working with reactive pipelines in the JDK.</p><h2 id="5-sample-processor-with-code-examples">5. Sample Processor with Code Examples</h2><p>Now that the foundations for your own implementation of a processor have been laid, it makes sense to look at typical patterns. They show how concrete use cases can be implemented and form the basis for more complex pipelines. Here are some simple but commonly used processor types, each with sample code.</p><h3 id="51-mapprocessor-io-transformation">5.1 MapProcessor &lt;I,O&gt; Transformation</h3><p>The MapProcessor is the simplest and at the same time one of the most useful processors. It takes on the role of a transformation: incoming data is changed with a function and then passed on. For example, strings can be converted to their length or numbers can be squared.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Test</span></span><span class="line"><span class="cl">  void mapProcessor_usage_minimal()</span></span><span class="line"><span class="cl">      throws Exception {</span></span><span class="line"><span class="cl">    final CountDownLatch done = new CountDownLatch(1);</span></span><span class="line"><span class="cl">    final Function<span class="nt">&lt;String</span><span class="err">,</span><span class="err">Integer</span><span class="nt">&gt;</span> mapper = s -&gt; Integer.parseInt(s) * 2;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//1) Publisher (source)</span></span><span class="line"><span class="cl">    try (SubmissionPublisher<span class="nt">&lt;String&gt;</span> publisher = new SubmissionPublisher<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//2) MapProcessor (String -&gt; Integer)</span></span><span class="line"><span class="cl">         MapProcessor<span class="nt">&lt;String</span><span class="err">,</span><span class="err">Integer</span><span class="nt">&gt;</span> map = new MapProcessor<span class="err">&lt;</span>&gt;(mapper)) {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//3) Target Subscribers (Spend Only)</span></span><span class="line"><span class="cl">      map.subscribe(</span></span><span class="line"><span class="cl">          new Flow.Subscriber<span class="err">&lt;</span>&gt;() {</span></span><span class="line"><span class="cl">            private Flow.Subscriptions;</span></span><span class="line"><span class="cl">            @Override</span></span><span class="line"><span class="cl">            public void onSubscribe(Flow.Subscription subscription) { this.s = subscription; s.request(1); }</span></span><span class="line"><span class="cl">            @Override</span></span><span class="line"><span class="cl">            public void onNext(Integer item) { out.println("receive = " + item); s.request(1); }</span></span><span class="line"><span class="cl">            @Override</span></span><span class="line"><span class="cl">            public void onError(Throwable t) { t.printStackTrace(); done.countDown(); }</span></span><span class="line"><span class="cl">            @Override</span></span><span class="line"><span class="cl">            public void onComplete() { done.countDown(); }</span></span><span class="line"><span class="cl">          });</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//Concatenation: Publisher -&gt; MapProcessor</span></span><span class="line"><span class="cl">      publisher.subscribe(map);</span></span><span class="line"><span class="cl">//Send data</span></span><span class="line"><span class="cl">      publisher.submit("1");</span></span><span class="line"><span class="cl">      publisher.submit("2");</span></span><span class="line"><span class="cl">      publisher.submit("3");</span></span><span class="line"><span class="cl">      publisher.close();</span></span><span class="line"><span class="cl">//signal end Wait briefly for completion (asynchronous)</span></span><span class="line"><span class="cl">      done.await();</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  /**</span></span><span class="line"><span class="cl">   * Minimal processor that applies a Function<span class="nt">&lt;I</span><span class="err">,O</span><span class="nt">&gt;</span>.</span></span><span class="line"><span class="cl">   * No own threads/executor – uses the defaults of the SubmissionPublisher.</span></span><span class="line"><span class="cl">   */</span></span><span class="line"><span class="cl">  static class MapProcessor<span class="nt">&lt;I</span><span class="err">,</span><span class="err">O</span><span class="nt">&gt;</span></span></span><span class="line"><span class="cl">      extends SubmissionPublisher<span class="nt">&lt;O&gt;</span></span></span><span class="line"><span class="cl">      implements Flow.Processor<span class="nt">&lt;I</span><span class="err">,</span><span class="err">O</span><span class="nt">&gt;</span> {</span></span><span class="line"><span class="cl">    private final Function<span class="nt">&lt;I</span><span class="err">,</span><span class="err">O</span><span class="nt">&gt;</span> mapper;</span></span><span class="line"><span class="cl">    private Flow.Subscription subscription;</span></span><span class="line"><span class="cl">    MapProcessor(Function<span class="nt">&lt;I</span><span class="err">,</span><span class="err">O</span><span class="nt">&gt;</span> mapper) {</span></span><span class="line"><span class="cl">      super();</span></span><span class="line"><span class="cl">      this.mapper = mapper;</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onSubscribe(Flow.Subscription subscription) { this.subscription = subscription; subscription.request(1); }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onNext(I item) { submit(mapper.apply(item)); subscription.request(1); }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onError(Throwable throwable) { closeExceptionally(throwable); }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onComplete() { close(); }</span></span><span class="line"><span class="cl">  } </span></span></code></pre></div></div><p>This allows data streams to be elegantly transformed without adjusting the rest of the pipeline.</p><h3 id="52-filterprocessor---filtering-data">5.2 FilterProcessor<T> – Filtering Data</h3><p>Another typical pattern is filtering. Only those elements that meet a certain condition are passed on. This is especially helpful when large amounts of data are generated, of which only a fraction is relevant.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Test</span></span><span class="line"><span class="cl">  void filterEvenNumbers()</span></span><span class="line"><span class="cl">      throws Exception {</span></span><span class="line"><span class="cl">    Predicate<span class="nt">&lt;Integer&gt;</span> even = n -&gt; {</span></span><span class="line"><span class="cl">      out.println("Filter: " + n);</span></span><span class="line"><span class="cl">      return n % 2 == 0;</span></span><span class="line"><span class="cl">    };</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    try (SubmissionPublisher<span class="nt">&lt;Integer&gt;</span> publisher = new SubmissionPublisher<span class="err">&lt;</span>&gt;()) {</span></span><span class="line"><span class="cl">      FilterProcessor<span class="nt">&lt;Integer&gt;</span> filter = new FilterProcessor<span class="err">&lt;</span>&gt;(even);</span></span><span class="line"><span class="cl">      CountDownLatch done = new CountDownLatch(1);</span></span><span class="line"><span class="cl">      CopyOnWriteArrayList<span class="nt">&lt;Integer&gt;</span> received = new CopyOnWriteArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">      Flow.Subscriber<span class="nt">&lt;Integer&gt;</span> subscriber = new Flow.Subscriber<span class="err">&lt;</span>&gt;() {</span></span><span class="line"><span class="cl">        private Flow.Subscription sub;</span></span><span class="line"><span class="cl">        @Override</span></span><span class="line"><span class="cl">        public void onSubscribe(Flow.Subscription subscription) {</span></span><span class="line"><span class="cl">          this.sub = subscription;</span></span><span class="line"><span class="cl">          sub.request(1);</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">        @Override</span></span><span class="line"><span class="cl">        public void onNext(Integer item) {</span></span><span class="line"><span class="cl">          received.add(item);</span></span><span class="line"><span class="cl">          sub.request(1);</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">        @Override</span></span><span class="line"><span class="cl">        public void onError(Throwable throwable) { done.countDown(); }</span></span><span class="line"><span class="cl">        @Override</span></span><span class="line"><span class="cl">        public void onComplete() { done.countDown(); }</span></span><span class="line"><span class="cl">      };</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">      publisher.subscribe(filter);</span></span><span class="line"><span class="cl">      filter.subscribe(subscriber);</span></span><span class="line"><span class="cl">      List.of(1, 2, 3, 4, 5, 6).forEach(publisher::submit);</span></span><span class="line"><span class="cl">      publisher.close();</span></span><span class="line"><span class="cl">      //Short waiting time for asynchronous processing</span></span><span class="line"><span class="cl">      if (!done.await(1, TimeUnit.SECONDS)) {</span></span><span class="line"><span class="cl">        throw new TimeoutException("Processing did not finish in time");</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">      out.println("received = " + received);</span></span><span class="line"><span class="cl">      assertEquals(List.of(2, 4, 6), received);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  static class FilterProcessor<span class="nt">&lt;T&gt;</span></span></span><span class="line"><span class="cl">      extends SubmissionPublisher<span class="nt">&lt;T&gt;</span></span></span><span class="line"><span class="cl">      implements Flow.Processor<span class="nt">&lt;T</span><span class="err">,</span><span class="err">T</span><span class="nt">&gt;</span> {</span></span><span class="line"><span class="cl">    private final Predicate<span class="nt">&lt;T&gt;</span> predicate;</span></span><span class="line"><span class="cl">    private Flow.Subscription subscription;</span></span><span class="line"><span class="cl">    public FilterProcessor(Predicate<span class="nt">&lt;T&gt;</span> predicate) { this.predicate = predicate; }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onSubscribe(Flow.Subscription subscription) {</span></span><span class="line"><span class="cl">      this.subscription = subscription;</span></span><span class="line"><span class="cl">      subscription.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onNext(T item) {</span></span><span class="line"><span class="cl">      if (predicate.test(item)) { submit(item); }</span></span><span class="line"><span class="cl">      subscription.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onError(Throwable throwable) { closeExceptionally(throwable); }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onComplete() { close(); }</span></span><span class="line"><span class="cl">  }</span></span></code></pre></div></div><p>With this processor, data streams can be reduced in a targeted manner and made more relevant.</p><h3 id="53-batchingprocessor---collect-and-share">5.3 BatchingProcessor<T> – Collect and Share</h3><p>Sometimes it doesn&rsquo;t make sense to pass on every single element right away. Instead, they want to collect data and pass it on in blocks. The BatchingProcessor does exactly this job.</p><p>This processor is useful for processing data efficiently in blocks, for example when writing to a database.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.util.*;</span></span><span class="line"><span class="cl">@Test</span></span><span class="line"><span class="cl">  void testBatch()</span></span><span class="line"><span class="cl">      throws InterruptedException {</span></span><span class="line"><span class="cl">    List<span class="nt">&lt;List</span><span class="err">&lt;Integer</span><span class="nt">&gt;</span>&gt; received = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">    CountDownLatch done = new CountDownLatch(1);</span></span><span class="line"><span class="cl">    try (SubmissionPublisher<span class="nt">&lt;Integer&gt;</span> publisher = new SubmissionPublisher<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">         BatchingProcessor<span class="nt">&lt;Integer&gt;</span> batching = new BatchingProcessor<span class="err">&lt;</span>&gt;(3)) {</span></span><span class="line"><span class="cl">      publisher.subscribe(batching);</span></span><span class="line"><span class="cl">      batching.subscribe(new Flow.Subscriber<span class="err">&lt;</span>&gt;() {</span></span><span class="line"><span class="cl">        private Flow.Subscription subscription;</span></span><span class="line"><span class="cl">        @Override</span></span><span class="line"><span class="cl">        public void onSubscribe(Flow.Subscription s) {</span></span><span class="line"><span class="cl">          this.subscription = s;</span></span><span class="line"><span class="cl">          s.request(1);</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        @Override</span></span><span class="line"><span class="cl">        public void onNext(List<span class="nt">&lt;Integer&gt;</span> batch) {</span></span><span class="line"><span class="cl">          System.out.println("onNext - batch = " + batch);</span></span><span class="line"><span class="cl">          received.add(batch);</span></span><span class="line"><span class="cl">          subscription.request(1);</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        @Override</span></span><span class="line"><span class="cl">        public void onError(Throwable t) {</span></span><span class="line"><span class="cl">          done.countDown();</span></span><span class="line"><span class="cl">          throw new AssertionError("Unexpected error", t);</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        @Override</span></span><span class="line"><span class="cl">        public void onComplete() {</span></span><span class="line"><span class="cl">          System.out.println("onComplete...");</span></span><span class="line"><span class="cl">          done.countDown();</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">      });</span></span><span class="line"><span class="cl">      for (int i = 1; i<span class="err">&lt;</span>= 10; i++) {</span></span><span class="line"><span class="cl">        System.out.println("submitting i = " + i);</span></span><span class="line"><span class="cl">        publisher.submit(i);</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">      publisher.close();</span></span><span class="line"><span class="cl">      done.await();</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    assertThat(received)</span></span><span class="line"><span class="cl">        .containsExactly(</span></span><span class="line"><span class="cl">            List.of(1, 2, 3),</span></span><span class="line"><span class="cl">            List.of(4, 5, 6),</span></span><span class="line"><span class="cl">            List.of(7, 8, 9),</span></span><span class="line"><span class="cl">            List.of(10)</span></span><span class="line"><span class="cl">        );</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  class BatchingProcessor<span class="nt">&lt;T&gt;</span></span></span><span class="line"><span class="cl">      extends SubmissionPublisher<span class="nt">&lt;List</span><span class="err">&lt;T</span><span class="nt">&gt;</span>&gt;</span></span><span class="line"><span class="cl">      implements Flow.Processor<span class="nt">&lt;T</span><span class="err">,</span><span class="err">List&lt;T</span><span class="nt">&gt;</span>&gt; {</span></span><span class="line"><span class="cl">    private final List<span class="nt">&lt;T&gt;</span> buffer = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">    private final int batchSize;</span></span><span class="line"><span class="cl">    private Flow.Subscription subscription;</span></span><span class="line"><span class="cl">    public BatchingProcessor(int batchSize) { this.batchSize = batchSize; }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onSubscribe(Flow.Subscription subscription) {</span></span><span class="line"><span class="cl">      this.subscription = subscription;</span></span><span class="line"><span class="cl">      subscription.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onNext(T item) {</span></span><span class="line"><span class="cl">      buffer.add(item);</span></span><span class="line"><span class="cl">      if (buffer.size() &gt;= batchSize) {</span></span><span class="line"><span class="cl">        submit(new ArrayList<span class="err">&lt;</span>&gt;(buffer));</span></span><span class="line"><span class="cl">        buffer.clear();</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">      subscription.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onError(Throwable throwable) { closeExceptionally(throwable); }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onComplete() {</span></span><span class="line"><span class="cl">      if (!buffer.isEmpty()) {</span></span><span class="line"><span class="cl">        submit(new ArrayList<span class="err">&lt;</span>&gt;(buffer));</span></span><span class="line"><span class="cl">        buffer.clear();</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">      close();</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span></code></pre></div></div><h3 id="54-throttleprocessor--throttling">5.4 ThrottleProcessor<T> Throttling</h3><p>Especially with high-frequency sources, it is necessary to limit the rate of data passed on. A ThrottleProcessor can be implemented in such a way that it only forwards one element at certain time intervals and discards the rest.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Test</span></span><span class="line"><span class="cl">  void throttle_allows_items_only_in_interval()</span></span><span class="line"><span class="cl">      throws Exception {</span></span><span class="line"><span class="cl">    try (SubmissionPublisher<span class="nt">&lt;Integer&gt;</span> publisher = new SubmissionPublisher<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">         ThrottleProcessor<span class="nt">&lt;Integer&gt;</span> throttle = new ThrottleProcessor<span class="err">&lt;</span>&gt;(50)) { // 50 ms interval</span></span><span class="line"><span class="cl">      CollectingSubscriber<span class="nt">&lt;Integer&gt;</span> subscriber = new CollectingSubscriber<span class="err">&lt;</span>&gt;(3); we expect 3 elements</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">      //Build a pipeline: publisher -&gt; throttle -&gt; subscriber</span></span><span class="line"><span class="cl">      publisher.subscribe(throttle);</span></span><span class="line"><span class="cl">      throttle.subscribe(subscriber);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">      //Send Items: 1 (should through), 2/3 (throttled), 4 (through), 5 (throttled), 6 (through)</span></span><span class="line"><span class="cl">      publisher.submit(1);</span></span><span class="line"><span class="cl">      Thread.sleep(10);</span></span><span class="line"><span class="cl">      publisher.submit(2);</span></span><span class="line"><span class="cl">      Thread.sleep(10);</span></span><span class="line"><span class="cl">      publisher.submit(3);</span></span><span class="line"><span class="cl">      Thread.sleep(60); //&gt; 50ms: Next may go through</span></span><span class="line"><span class="cl">      publisher.submit(4);</span></span><span class="line"><span class="cl">      Thread.sleep(10);</span></span><span class="line"><span class="cl">      publisher.submit(5);</span></span><span class="line"><span class="cl">      Thread.sleep(60);</span></span><span class="line"><span class="cl">      publisher.submit(6);</span></span><span class="line"><span class="cl">      publisher.close(); //Completion of the source</span></span><span class="line"><span class="cl">      assertTrue(subscriber.await(2, TimeUnit.SECONDS),</span></span><span class="line"><span class="cl">                 "Timeout while waiting for throttled items");</span></span><span class="line"><span class="cl">      var received = subscriber.getReceived();</span></span><span class="line"><span class="cl">      logger().info("Received {} items", received);</span></span><span class="line"><span class="cl">      assertEquals(List.of(1, 4, 6), received);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  //---- Processor ----</span></span><span class="line"><span class="cl">  static class ThrottleProcessor<span class="nt">&lt;T&gt;</span></span></span><span class="line"><span class="cl">      extends SubmissionPublisher<span class="nt">&lt;T&gt;</span></span></span><span class="line"><span class="cl">      implements Flow.Processor<span class="nt">&lt;T</span><span class="err">,</span><span class="err">T</span><span class="nt">&gt;</span>, HasLogger {</span></span><span class="line"><span class="cl">    private final long intervalMillis;</span></span><span class="line"><span class="cl">    private Flow.Subscription subscription;</span></span><span class="line"><span class="cl">    private long lastEmission = 0;</span></span><span class="line"><span class="cl">    public ThrottleProcessor(long intervalMillis) {</span></span><span class="line"><span class="cl">      this.intervalMillis = intervalMillis;</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onSubscribe(Flow.Subscription subscription) {</span></span><span class="line"><span class="cl">      this.subscription = subscription;</span></span><span class="line"><span class="cl">      subscription.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onNext(T item) {</span></span><span class="line"><span class="cl">      logger().info("onNext {}", item);</span></span><span class="line"><span class="cl">      long now = System.currentTimeMillis();</span></span><span class="line"><span class="cl">      if (now - lastEmission &gt;= intervalMillis) {</span></span><span class="line"><span class="cl">        logger().info("submit item " + item);</span></span><span class="line"><span class="cl">        submit(item);</span></span><span class="line"><span class="cl">        lastEmission = now;</span></span><span class="line"><span class="cl">      } else {</span></span><span class="line"><span class="cl">        logger().info("<span class="nt">&lt; intervalMillis</span><span class="err">-</span><span class="err">skipping</span><span class="err">item</span><span class="err">{}",</span><span class="err">item);</span></span></span><span class="line"><span class="cl">     <span class="err">}</span></span></span><span class="line"><span class="cl">     <span class="err">subscription.request(1);</span></span></span><span class="line"><span class="cl">   <span class="err">}</span></span></span><span class="line"><span class="cl">   <span class="err">@Override</span></span></span><span class="line"><span class="cl">   <span class="err">public</span><span class="err">void</span><span class="err">onError(Throwable</span><span class="err">throwable)</span><span class="err">{</span><span class="err">closeExceptionally(throwable);</span><span class="err">}</span></span></span><span class="line"><span class="cl">   <span class="err">@Override</span></span></span><span class="line"><span class="cl">   <span class="err">public</span><span class="err">void</span><span class="err">onComplete()</span><span class="err">{</span><span class="err">close();</span><span class="err">}</span></span></span><span class="line"><span class="cl"> <span class="err">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> <span class="err">//----</span><span class="err">simple</span><span class="err">collector-subscriber</span><span class="err">----</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> <span class="err">static</span><span class="err">class</span><span class="err">CollectingSubscriber&lt;T</span><span class="nt">&gt;</span></span></span><span class="line"><span class="cl">      implements Flow.Subscriber<span class="nt">&lt;T&gt;</span> , HasLogger {</span></span><span class="line"><span class="cl">    private final List<span class="nt">&lt;T&gt;</span> received = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">    private final CountDownLatch latch;</span></span><span class="line"><span class="cl">    private Flow.Subscription subscription;</span></span><span class="line"><span class="cl">    CollectingSubscriber(int expectedCount) {</span></span><span class="line"><span class="cl">      this.latch = new CountDownLatch(expectedCount);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onSubscribe(Flow.Subscription subscription) {</span></span><span class="line"><span class="cl">      this.subscription = subscription;</span></span><span class="line"><span class="cl">      subscription.request(Long.MAX_VALUE); unbounded demand</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onNext(T item) {</span></span><span class="line"><span class="cl">      logger().info("onNext {}", item);</span></span><span class="line"><span class="cl">      received.add(item);</span></span><span class="line"><span class="cl">      latch.countDown();</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onError(Throwable throwable) { /* no-op for test */ }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onComplete() { /* no-op for test */ }</span></span><span class="line"><span class="cl">    List<span class="nt">&lt;T&gt;</span> getReceived() { return received; }</span></span><span class="line"><span class="cl">    boolean await(long timeout, TimeUnit unit)</span></span><span class="line"><span class="cl">        throws InterruptedException {</span></span><span class="line"><span class="cl">      return latch.await(timeout, unit);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  } </span></span></code></pre></div></div><p>This pattern can be used, for example, to prevent a UI event stream from generating too many updates uncontrollably.</p><p>The sample processors presented here are fundamental building blocks for designing reactive pipelines in the JDK. With transformation, filtering, batching and throttling, many typical requirements are covered. The following chapters will focus on implementing backpressure in practice and discuss which strategies have proven successful in operation.</p><h2 id="6-backpressure-in-practice">6. Backpressure in practice</h2><p>After presenting concrete sample processors in the previous chapter, we will now focus on one of the central topics in the Flow API: Backpressure. While the theory sounds simple – the subscriber determines the number of elements they can process – in practice, it turns out that the right implementation is crucial for stability, efficiency and robustness.</p><h3 id="61-strategies-for-dealing-with-backpressure">6.1 Strategies for dealing with backpressure</h3><p>Backpressure prevents a publisher from flooding its subscribers with data. Nevertheless, there are different strategies for coping with demand:</p><ul><li><strong>Bounded buffering:</strong> In this strategy, the publisher only keeps a clearly defined, limited number of elements in an internal buffer. Once this buffer is filled, there are several possible reactions: the submit() method can block until there is space again, or it throws an exception to indicate the overload. In this way, an uncontrolled growth of the memory is prevented. This model is particularly suitable in scenarios where a controlled and predictable data rate is required – for example, in systems that are only allowed to process a certain number of requests per second or in hardware interfaces with limited processing capacity.</li><li><strong>Dropping:</strong> When overloaded, new elements are deliberately discarded instead of being cached in an infinite buffer. This strategy makes sense, especially where not every single event is critical and the loss of individual data points remains tolerable. Typical examples are telemetry data, which is generated at a high frequency anyway, or UI events such as mouse movements or keystrokes, where only the current state is relevant for further processing. Dropping keeps the system responsive and resource-efficient even under extreme load, as it doesn&rsquo;t try to funnel every single event through the pipeline at all costs.</li><li><strong>Latest-Value:</strong> In the Latest-Value strategy, old elements are not collected in a buffer, but are consistently discarded as soon as a new value arrives. Only the most recently delivered element is stored in each case, so that the subscriber only receives the latest version the next time it is retrieved or asked. This procedure is beneficial when values change continuously. Still, only the last state is relevant for processing – for example, in cases where sensor data, such as temperature or position coordinates, is involved. This relieves the subscriber because he does not have to work through all the intermediate results, but can continue working directly with the latest information.</li><li><strong>Blocking:</strong> In the blocking strategy, the publisher actively waits for demand to be signalled again by the subscriber before delivering further elements. In practice, this means that the calling thread blocks until a corresponding request is made. This approach is comparatively easy to implement and makes the process calculable at first glance, as it ensures that no more data is produced than was requested. However, this approach has a significant drawback: blocked threads are a scarce resource in highly concurrent systems, and when many publishers block at the same time, entire thread pools can become clogged. Therefore, blocking should only be used in very special scenarios, such as when the data rate is low anyway or when controlling the exact data flow is more important than maximum parallelism.</li></ul><h3 id="62-demand-policy-batch-vs-single-retrieval">6.2 Demand Policy: Batch vs. Single Retrieval</h3><p>Subscribers can determine for themselves how many items they call up at a time. There are two common approaches:</p><ul><li><strong>Single retrieval ( request(1) ):</strong> After each item received, exactly one more is requested. This approach is simple and provides maximum control, but it generates a large number of signals and can become inefficient at very high rates.</li><li><strong>Batch retrieval ( request(n) ):</strong> The subscriber requests several elements at once, such as request(32). This reduces the signal load, allowing the publisher to deliver items in blocks more efficiently. However, the subscriber must then make sure that he can handle these batches.</li></ul><p>In practice, both approaches are often combined, depending on whether low latency or high throughput is the primary concern.</p><h3 id="63-monitoring-tuning-and-capacity-planning">6.3 Monitoring, Tuning and Capacity Planning</h3><p>A decisive success factor in the use of backpressure is monitoring. Without measuring points, it remains unclear whether a system is running stably or is already working at its limits. The following key figures are helpful:</p><ul><li><strong>Queue depth:</strong> The queue depth refers to the number of elements currently in the internal buffer that the subscriber has not yet retrieved. It is a direct indicator of whether the publisher is rushing away from the consumer or whether demand is in balance with production.</li><li><strong>Latency:</strong> Latency refers to the time elapsed between the creation of an element and its final processing by the subscriber. It is a key measure of the system&rsquo;s responsiveness: high latency indicates that elements are jamming in buffers or processing steps are taking too long. In contrast, low latency means a smooth data flow.</li><li><strong>Throughput (items per second):</strong> Throughput indicates the number of items that can be processed per unit of time. It&rsquo;s a measure of the entire pipeline&rsquo;s performance: high throughput means that publishers, processors, and subscribers can work together efficiently and meet demand. If the throughput drops below the expected level, this is an indication of a bottleneck – whether due to too small buffers, too slow processing in the subscriber, or insufficient parallelisation. Throughput is therefore an important key figure for realistically estimating the capacity of a system and making targeted optimisations.</li></ul><p>This data can be used to identify bottlenecks and take appropriate measures, such as adjusting the batch size, increasing the buffers, or introducing additional processor stages.</p><h2 id="concurrency--execution-context">Concurrency &amp; Execution Context</h2><p>A central feature of the Flow API is its close integration with concurrent processing. Unlike classic Java streams, which usually run synchronously in the same thread, reactive streams almost always involve an asynchronous interaction of several threads. Therefore, it is crucial to understand the role of the execution context in detail.</p><h3 id="71-executor-strategies">7.1 Executor Strategies</h3><p>The Flow API itself does not specify on which threads the signals are processed. Instead, the implementations determine which executor they use. The SubmissionPublisher uses the ForkJoinPool.commonPool() by default, but also allows you to include your own executor. This is extremely important in practice, as the choice of the executor can change the behaviour of the entire system:</p><ul><li>A<strong>CachedThreadPool</strong> can enable very high parallelism through its dynamic generation and reuse of threads. It grows indefinitely when there are many tasks at the same time, and reduces again when there is less work. This makes it exceptionally flexible and well-suited for unpredictable load peaks. However, this dynamic can also lead to uncontrollable behaviour: If thousands of threads are suddenly created, the context switching load increases significantly, which has an adverse effect on latency. It is therefore less suitable for latency-critical scenarios, as response times can become unpredictable due to the administrative overhead and the potentially high number of threads.</li><li>A<strong>FixedThreadPool</strong> works with a fixed number of threads, which are defined when the pool is created. In this way, it creates predictable limits for concurrency and prevents uncontrolled growth of the thread count. This makes it particularly suitable for scenarios in which resources such as CPU cores or memory are to be used in a clearly limited and predictable manner. However, there is a disadvantage when overloaded: If there are more tasks than threads available, queues form. These lead to increasing latencies and can become problematic in critical systems if the queue grows unchecked. It is therefore essential to consciously dimension the pool and possibly use mechanisms such as backpressure or rejection policies to absorb overload situations in a controlled manner.</li><li>The<strong>ForkJoinPool</strong> is optimised for fine-grained, recursive, and highly parallelizable tasks. He relies on a work-stealing procedure in which worker threads actively search for functions that are in the queues of other threads when they themselves have no more work. This achieves a high utilisation of the available threads and makes efficient use of computing time. This model excels above all in CPU-intensive calculations, which can be broken down into many small, independent tasks – for example, in divide-and-conquer algorithms or parallelised data analyses. The ForkJoinPool, on the other hand, is less suitable for blocking operations such as network access, file system or database queries. When a worker thread blocks, it becomes unavailable for work stealing, making the pool scarce, and the model’s efficiency suffers. In such cases, it is advisable to outsource blocking work to separate executors with a suitable thread policy or to rely on the virtual threads introduced by Loom, which are more tolerant of blocking operations. Additionally, in specific scenarios, the use of ForkJoinPool is applicable. ManagedBlocker can help to mitigate the adverse effects of blocking operations by allowing the pool size to be temporarily extended.</li></ul><p>Choosing the right executor is, therefore, a key architectural point that directly impacts throughput, latency, and stability.</p><h3 id="72-reentrancy-traps-and-serialization-of-signals">7.2 Reentrancy traps and serialization of signals</h3><p>Another critical issue is the question of how the onNext, onError and onComplete signals are delivered. The Reactive Streams specification specifies that these signals must be called sequentially and not simultaneously, so they must be serialised. Nevertheless, in practice, parallel calls arise due to your own implementations or clumsy synchronisation. This quickly leads to errors that are difficult to reproduce, the so-called &ldquo;Heisenbugs&rdquo;.</p><p>Therefore, a processor or subscriber must ensure that he is not confronted with onNext calls from several threads at the same time. This can be achieved through synchronisation, using queues or by delegation to an executor, which processes the signals one after the other.</p><h3 id="73-virtual-threads-loom-vs-reactive">7.3 Virtual Threads (Loom) vs. Reactive</h3><p>With Project Loom, Java has introduced a new model: Virtual Threads. These enable the creation of millions of lightweight threads that efficiently support blocking operations. This blurs the classic line between blocking and non-blocking code. The question, therefore, arises: Do we still need reactive streams with complex backpressure at all?</p><p>The answer is: Yes, but differentiated. While virtual threads make it easier to handle multiple simultaneous operations, they do not automatically resolve the issue of uncontrolled data production. Even with Virtual Threads, a subscriber must be able to specify the number of elements they can process. Backpressure, therefore, remains an important concept. However, virtual threads can help simplify the implementation of subscriber logic, as blocking processing steps now scale much better.</p><h2 id="error--completion-scenarios">Error &amp; Completion Scenarios</h2><p>In every reactive data stream, the question arises as to how to deal with errors and the regular closing. The Flow API provides clear semantics for this: Each stream ends either with a successful completion (onComplete) or with an error (onError). This simple but strict model ensures that a subscriber knows exactly what state the data flow is in at all times.</p><h3 id="81-semantics-of-onerror-and-oncomplete">8.1 Semantics of onError and onComplete</h3><p>The onError and onComplete methods mark the endpoint of a data stream. Once one of these methods has been called, no further data may be sent through onNext. This contract is crucial because it guarantees consistency: a subscriber can rest assured that they won&rsquo;t have to process any new items at the end.</p><ul><li><strong>onComplete</strong> means that all elements have been successfully submitted and the source has been exhausted. Examples include reading a file or playing back a finite list of messages.</li><li><strong>onError</strong> signals that an error occurred during processing. This error is passed on to the subscriber as an exception so that he can react in a targeted manner – for example, through logging, restarts, or emergency measures.</li></ul><p>It is important to note that onError and onComplete are mutually exclusive. So it must never happen that a stream first breaks off with an error and then signals a regular end.</p><h3 id="82-restarts-and-retry-mechanisms">8.2 Restarts and Retry Mechanisms</h3><p>In many use cases, it is not sufficient to simply terminate a stream at the first error. Instead, they want to try to resume processing. Classic examples are network requests or database access, which can fail temporarily.</p><p>There is no built-in retry functionality in the flow API itself, but it can be implemented at the processor level. For example, after an error, a processor could decide to restart the request or skip the faulty record. It is important to handle the state clearly: A new onSubscribe may only take place when a new data stream is started. For more complex scenarios, patterns such as circuit breaker or retry with exponential backoff are ideal.</p><p>A simple example could be a subscriber who raises a counter on the first error and retries the faulty operation a maximum of three times. Only if an error still occurs after these retries does it finally pass the error on to the downstream:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">class RetrySubscriber<span class="nt">&lt;T&gt;</span> implements Flow.Subscriber<span class="nt">&lt;T&gt;</span> {</span></span><span class="line"><span class="cl">    private final Flow.Subscriber<span class="err">&lt;</span>? super T&gt; downstream;</span></span><span class="line"><span class="cl">    private int retries = 0;</span></span><span class="line"><span class="cl">    private static final int MAX_RETRIES = 3;</span></span><span class="line"><span class="cl">    private Flow.Subscription subscription;</span></span><span class="line"><span class="cl">    RetrySubscriber(Flow.Subscriber<span class="err">&lt;</span>? super T&gt; downstream) {</span></span><span class="line"><span class="cl">        this.downstream = downstream;</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onSubscribe(Flow.Subscription subscription) {</span></span><span class="line"><span class="cl">        this.subscription = subscription;</span></span><span class="line"><span class="cl">        downstream.onSubscribe(subscription);</span></span><span class="line"><span class="cl">        subscription.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onNext(T item) {</span></span><span class="line"><span class="cl">        downstream.onNext(item);</span></span><span class="line"><span class="cl">        subscription.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onError(Throwable t) {</span></span><span class="line"><span class="cl">        if (retries<span class="nt">&lt; MAX_RETRIES</span><span class="err">)</span><span class="err">{</span></span></span><span class="line"><span class="cl">           <span class="err">retries++;</span></span></span><span class="line"><span class="cl">           <span class="err">System.out.println("Retry</span><span class="err">"</span><span class="err">+</span><span class="err">retries</span><span class="err">+</span><span class="err">"</span><span class="err">after</span><span class="err">error:</span><span class="err">"</span><span class="err">+</span><span class="err">t.getMessage());</span></span></span><span class="line"><span class="cl">           <span class="err">subscription.request(1);</span><span class="err">Trying</span><span class="err">to</span><span class="err">keep</span><span class="err">going</span></span></span><span class="line"><span class="cl">       <span class="err">}</span><span class="err">else</span><span class="err">{</span></span></span><span class="line"><span class="cl">           <span class="err">downstream.onError(t);</span></span></span><span class="line"><span class="cl">       <span class="err">}</span></span></span><span class="line"><span class="cl">   <span class="err">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   <span class="err">@Override</span></span></span><span class="line"><span class="cl">   <span class="err">public</span><span class="err">void</span><span class="err">onComplete()</span><span class="err">{</span></span></span><span class="line"><span class="cl">       <span class="err">downstream.onComplete();</span></span></span><span class="line"><span class="cl">   <span class="err">}</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span></code></pre></div></div><p>This simple pattern is of course greatly simplified, but it clarifies the principle of a repeated attempt in the event of temporary errors.</p><h3 id="83-idem-potency-and-exactly-once-processing">8.3 Idem potency and exactly-once processing</h3><p>A central problem with restarts is determining whether operations can be carried out more than once. Many systems are only robust if their operations are<strong>idempotent</strong> – that is, they produce the same result when executed multiple times. An example is writing a record with a specific key to a database: even if this process is repeated, the final state remains the same.</p><p>If idempotency is absent, there is a risk of double processing, which can lead to incorrect results or inconsistencies. Therefore, it is good practice to prefer idempotent operations or to ensure that data is not processed multiple times through additional mechanisms, such as transaction IDs, deduplication, or exactly-once processing.</p><p>A simple example: Suppose a processor processes orders and passes each order with a unique ID to an OrderService, which stores it. Even if the same record passes through the processor twice, the ID ensures that it only exists once in the service. This means that the operation remains idempotent.</p><p>A small Java example with a<strong>processor</strong> and JUnit5Test might look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import org.junit.jupiter.api.Test;</span></span><span class="line"><span class="cl">import static org.junit.jupiter.api.Assertions.*;</span></span><span class="line"><span class="cl">import java.util.*;</span></span><span class="line"><span class="cl">import java.util.concurrent.*;</span></span><span class="line"><span class="cl">import java.util.function.Function;</span></span><span class="line"><span class="cl">  @Test</span></span><span class="line"><span class="cl">  void processor_enforces_idempotence_by_filtering_duplicates()</span></span><span class="line"><span class="cl">      throws Exception {</span></span><span class="line"><span class="cl">    OrderService svc = new OrderService();</span></span><span class="line"><span class="cl">    try (SubmissionPublisher<span class="nt">&lt;Order&gt;</span> pub = new SubmissionPublisher<span class="err">&lt;</span>&gt;()) {</span></span><span class="line"><span class="cl">      DeduplicateProcessor<span class="nt">&lt;Order</span><span class="err">,</span><span class="err">String</span><span class="nt">&gt;</span> dedup = new DeduplicateProcessor<span class="err">&lt;</span>&gt;(Order::id);</span></span><span class="line"><span class="cl">      pub.subscribe(dedup);</span></span><span class="line"><span class="cl">      dedup.subscribe(new SinkSubscriber(svc));</span></span><span class="line"><span class="cl">      pub.submit(new Order("order-1", "Order A"));</span></span><span class="line"><span class="cl">      pub.submit(new Order("order-1", "Order A (Duplicate)"));</span></span><span class="line"><span class="cl">      pub.submit(new Order("order-2", "Order B"));</span></span><span class="line"><span class="cl">      pub.close();</span></span><span class="line"><span class="cl">      Thread.sleep(100); asynchronous drain</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    assertEquals(2, svc.size());</span></span><span class="line"><span class="cl">    assertNotNull(svc.get("order-1"));</span></span><span class="line"><span class="cl">    assertNotNull(svc.get("order-2"));</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  //Domain</span></span><span class="line"><span class="cl">  record Order(String id, String payload) { }</span></span><span class="line"><span class="cl">  //Processor: filters duplicates based on a key (idempotency in the pipeline)</span></span><span class="line"><span class="cl">  static class DeduplicateProcessor<span class="nt">&lt;T</span><span class="err">,</span><span class="err">K</span><span class="nt">&gt;</span></span></span><span class="line"><span class="cl">      extends SubmissionPublisher<span class="nt">&lt;T&gt;</span></span></span><span class="line"><span class="cl">      implements Flow.Processor<span class="nt">&lt;T</span><span class="err">,</span><span class="err">T</span><span class="nt">&gt;</span> , HasLogger {</span></span><span class="line"><span class="cl">    private final Function<span class="nt">&lt;T</span><span class="err">,</span><span class="err">K</span><span class="nt">&gt;</span> keyExtractor;</span></span><span class="line"><span class="cl">    private final Set<span class="nt">&lt;K&gt;</span> seen = ConcurrentHashMap.newKeySet();</span></span><span class="line"><span class="cl">    private Flow.Subscription subscription;</span></span><span class="line"><span class="cl">    DeduplicateProcessor(Function<span class="nt">&lt;T</span><span class="err">,</span><span class="err">K</span><span class="nt">&gt;</span> keyExtractor) { this.keyExtractor = keyExtractor; }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onSubscribe(Flow.Subscription s) {</span></span><span class="line"><span class="cl">      this.subscription = s;</span></span><span class="line"><span class="cl">      s.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onNext(T item) {</span></span><span class="line"><span class="cl">      logger().info("Receive {}", item);</span></span><span class="line"><span class="cl">      if (seen.add(keyExtractor.apply(item))) {</span></span><span class="line"><span class="cl">        logger().info("no duplicate - submitting");</span></span><span class="line"><span class="cl">        submit(item);</span></span><span class="line"><span class="cl">      } else {</span></span><span class="line"><span class="cl">        logger().info("Duplicate - ignore");</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">      subscription.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onError(Throwable t) { closeExceptionally(t); }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onComplete() { close(); }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  //Subscriber writes to a simple "DB"</span></span><span class="line"><span class="cl">  static class OrderService {</span></span><span class="line"><span class="cl">    private final Map<span class="nt">&lt;String</span><span class="err">,</span><span class="err">Order</span><span class="nt">&gt;</span> store = new ConcurrentHashMap<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">    void save(Order o) { store.put(o.id(), o); }</span></span><span class="line"><span class="cl">    Order get(String id) { return store.get(id); }</span></span><span class="line"><span class="cl">    int size() { return store.size(); }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  static class SinkSubscriber</span></span><span class="line"><span class="cl">      implements Flow.Subscriber<span class="nt">&lt;Order&gt;</span>, HasLogger {</span></span><span class="line"><span class="cl">    private final OrderService svc;</span></span><span class="line"><span class="cl">    private Flow.Subscriptions;</span></span><span class="line"><span class="cl">    SinkSubscriber(OrderService svc) { this.svc = svc; }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onSubscribe(Flow.Subscription s) {</span></span><span class="line"><span class="cl">      this.s = s;</span></span><span class="line"><span class="cl">      s.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onNext(Order item) {</span></span><span class="line"><span class="cl">      logger().info("Save {}", item);</span></span><span class="line"><span class="cl">      svc.save(item);</span></span><span class="line"><span class="cl">      s.request(1);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onError(Throwable t) { }</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    public void onComplete() { }</span></span><span class="line"><span class="cl">  }</span></span></code></pre></div></div><p>The test shows that despite the publication of order-1 being repeated twice, only one instance is sent downstream, thanks to<strong>DeduplicateProcessor</strong>. The idempotency logic is thus in the<strong>processor</strong> , not in the subscriber.</p><h2 id="interoperability--integration">Interoperability &amp; Integration</h2><p>A key feature of the Flow API is its openness to integration. The API itself is minimal and only defines the basic contracts for publisher, subscriber, subscription and processor. This allows it to be used both standalone and combined with other reactive libraries.</p><h3 id="91-integration-with-reactive-frameworks">9.1 Integration with Reactive Frameworks</h3><p>Many established reactive frameworks, such as<strong>Project Reactor</strong> ,<strong>RxJava,</strong> or<strong>Akka Streams,</strong> are based on the same fundamental ideas as the Flow API and are closely tied to the official Reactive Streams specification. They offer either direct adapters or interfaces that can be used to integrate publishers, subscribers and, in particular, your own processor implementations. This means that self-developed building blocks can be easily embedded in complex ecosystems without being tied to a specific framework. A central example is the helper class FlowAdapters in the JDK, which provides conversion methods between java.util.concurrent.Flow and org.reactivestreams.Publisher. This enables seamless integration of your own JDK-based publishers or processors with Reactor or RxJava operators without breaking the contracts. In practice, this means that a processor implemented with Flow can be integrated directly into a Reactor flux or fed from an RxJava observable, allowing existing pipelines to be expanded or migrated step by step.</p><p>A simple example shows the use of FlowAdapters. This example can also be secured with a small JUnit5 test:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import org.junit.jupiter.api.Test;</span></span><span class="line"><span class="cl">import static org.junit.jupiter.api.Assertions.*;</span></span><span class="line"><span class="cl">import java.util.concurrent.*;</span></span><span class="line"><span class="cl">import java.util.concurrent.Flow.*;</span></span><span class="line"><span class="cl">import java.util.concurrent.FlowAdapters;</span></span><span class="line"><span class="cl">public class FlowAdapterTest {</span></span><span class="line"><span class="cl">    @Test</span></span><span class="line"><span class="cl">    void testFlowAdapterIntegration() throws Exception {</span></span><span class="line"><span class="cl">        try (SubmissionPublisher<span class="nt">&lt;String&gt;</span> jdkPublisher = new SubmissionPublisher<span class="err">&lt;</span>&gt;()) {</span></span><span class="line"><span class="cl">            // Flow -&gt; Reactive Streams</span></span><span class="line"><span class="cl">            org.reactivestreams.Publisher<span class="nt">&lt;String&gt;</span> rsPublisher = FlowAdapters.toPublisher(jdkPublisher);</span></span><span class="line"><span class="cl">            //back to Flow</span></span><span class="line"><span class="cl">            Publisher<span class="nt">&lt;String&gt;</span> flowPublisher = FlowAdapters.toFlowPublisher(rsPublisher);</span></span><span class="line"><span class="cl">            List<span class="nt">&lt;String&gt;</span> results = Collections.synchronizedList(new ArrayList<span class="err">&lt;</span>&gt;());</span></span><span class="line"><span class="cl">            flowPublisher.subscribe(new Subscriber<span class="err">&lt;</span>&gt;() {</span></span><span class="line"><span class="cl">                private subscriptions;</span></span><span class="line"><span class="cl">                @Override public void onSubscribe(Subscription s) { this.s = s; s.request(1); }</span></span><span class="line"><span class="cl">                @Override public void onNext(String item) { results.add(item); s.request(1); }</span></span><span class="line"><span class="cl">                @Override public void onError(Throwable t) { fail(t); }</span></span><span class="line"><span class="cl">                @Override public void onComplete() { }</span></span><span class="line"><span class="cl">            });</span></span><span class="line"><span class="cl">            jdkPublisher.submit("Hello World");</span></span><span class="line"><span class="cl">            jdkPublisher.close();</span></span><span class="line"><span class="cl">            Thread.sleep(100); Wait asynchronously</span></span><span class="line"><span class="cl">            assertEquals(List.of("Hello World"), results);</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This test shows that a SubmissionPublisher can be successfully turned into a Reactive Streams publisher and back again via FlowAdapters. The message &ldquo;Hello world&rdquo; reaches the subscriber correctly at the end.</p><h3 id="92-use-in-classic-applications">9.2 Use in classic applications</h3><p>The Flow API can also be used sensibly in non-reactive applications. For example, data streams from a message queue or a websocket can be distributed to internal services via a SubmissionPublisher. Legacy systems that have previously relied on polling benefit from distributing and processing events in real-time. This enables modern, reactive architectures to be integrated into existing applications incrementally.</p><p>A simple example illustrates the connection with a legacy service:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.util.concurrent.SubmissionPublisher;</span></span><span class="line"><span class="cl">import java.util.concurrent.Flow;</span></span><span class="line"><span class="cl">//Simulates an existing legacy service that was previously addressed via polling</span></span><span class="line"><span class="cl">class LegacyService {</span></span><span class="line"><span class="cl">    public void process(String msg) {</span></span><span class="line"><span class="cl">        System.out.println("LegacyService handles: " + msg);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl">//Subscriber who passes incoming events to the legacy service</span></span><span class="line"><span class="cl">class LegacyServiceSubscriber implements Flow.Subscriber<span class="nt">&lt;String&gt;</span> {</span></span><span class="line"><span class="cl">    private Flow.Subscriptions;</span></span><span class="line"><span class="cl">    private final LegacyService service;</span></span><span class="line"><span class="cl">    LegacyServiceSubscriber(LegacyService service) { this.service = service; }</span></span><span class="line"><span class="cl">    @Override public void onSubscribe(Flow.Subscription s) { this.s = s; s.request(1); }</span></span><span class="line"><span class="cl">    @Override public void onNext(String item) { service.process(item); s.request(1); }</span></span><span class="line"><span class="cl">    @Override public void onError(Throwable t) { t.printStackTrace(); }</span></span><span class="line"><span class="cl">    @Override public void onComplete() { System.out.println("Stream completed"); }</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public class LegacyIntegrationDemo {</span></span><span class="line"><span class="cl">    public static void main(String[] args) {</span></span><span class="line"><span class="cl">        LegacyService legacy = new LegacyService();</span></span><span class="line"><span class="cl">        try (SubmissionPublisher<span class="nt">&lt;String&gt;</span> pub = new SubmissionPublisher<span class="err">&lt;</span>&gt;()) {</span></span><span class="line"><span class="cl">            pub.subscribe(new LegacyServiceSubscriber(legacy));</span></span><span class="line"><span class="cl">            //Example: instead of polling, a MessageQueue delivers new messages</span></span><span class="line"><span class="cl">            pub.submit("Message 1 from MQ");</span></span><span class="line"><span class="cl">            pub.submit("Message 2 from MQ");</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This small program simulates use in a classic application: Instead of polling, messages from an external source are immediately forwarded to the LegacyService , which can continue to work unchanged. The Flow API serves as a bridge between modern event processing and existing logic.</p><h3 id="93-bridge-to-java-streams">9.3 Bridge to Java Streams</h3><p>A common question is how the Flow API compares to or even connects to classic Java Streams (the Stream API). While streams typically process finite amounts of data in the pull model, flow works with potentially infinite sequences in the push model. Nevertheless, a combination is possible: A processor could cache incoming data and feed it into a Java stream. Conversely, the results of a stream can be brought back into a reactive context via a publisher. This creates a bridge between batch-oriented and event-driven processing.</p><p>A small JUnit5 example demonstrates this bridge: Here, elements are transferred to a stream via a SubmissionPublisher and then summed.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import org.junit.jupiter.api.Test;</span></span><span class="line"><span class="cl">import static org.junit.jupiter.api.Assertions.*;</span></span><span class="line"><span class="cl">import java.util.concurrent.*;</span></span><span class="line"><span class="cl">import java.util.*;</span></span><span class="line"><span class="cl">public class FlowToStreamTest {</span></span><span class="line"><span class="cl">    @Test</span></span><span class="line"><span class="cl">    void testFlowToStreamIntegration() throws Exception {</span></span><span class="line"><span class="cl">        try (SubmissionPublisher<span class="nt">&lt;Integer&gt;</span> publisher = new SubmissionPublisher<span class="err">&lt;</span>&gt;()) {</span></span><span class="line"><span class="cl">            List<span class="nt">&lt;Integer&gt;</span> buffer = Collections.synchronizedList(new ArrayList<span class="err">&lt;</span>&gt;());</span></span><span class="line"><span class="cl">            //Subscriber collects data in a list</span></span><span class="line"><span class="cl">            publisher.subscribe(new Flow.Subscriber<span class="err">&lt;</span>&gt;() {</span></span><span class="line"><span class="cl">                private Flow.Subscriptions;</span></span><span class="line"><span class="cl">                @Override public void onSubscribe(Flow.Subscription s) { this.s = s; s.request(1); }</span></span><span class="line"><span class="cl">                @Override public void onNext(Integer item) { buffer.add(item); s.request(1); }</span></span><span class="line"><span class="cl">                @Override public void onError(Throwable t) { t.printStackTrace(); }</span></span><span class="line"><span class="cl">                @Override public void onComplete() { }</span></span><span class="line"><span class="cl">            });</span></span><span class="line"><span class="cl">            Publish events</span></span><span class="line"><span class="cl">            publisher.submit(1);</span></span><span class="line"><span class="cl">            publisher.submit(2);</span></span><span class="line"><span class="cl">            publisher.submit(3);</span></span><span class="line"><span class="cl">            publisher.close();</span></span><span class="line"><span class="cl">            //short pause to wait for asynchronous collection</span></span><span class="line"><span class="cl">            Thread.sleep(100);</span></span><span class="line"><span class="cl">            //Transition to Java Stream: Calculate Sum</span></span><span class="line"><span class="cl">            int sum = buffer.stream().mapToInt(i -&gt; i).sum();</span></span><span class="line"><span class="cl">            assertEquals(6, sum);</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>In this example, the list acts as a buffer and allows the transition from the asynchronous push model (flow) to the synchronous pull model (stream). In this way, both worlds can be elegantly combined. However, the topic is far more extensive and will be examined in detail in a separate article.</p><h2 id="result">Result</h2><p>The Flow API in<code>java.util.Concurrent</code> shows how Java provides a clean, standardised basis for reactive data streams. While Java Streams are intended for finite, synchronous pull scenarios, Flow addresses the challenges of continuous, infinite and asynchronous data sources. Core concepts include<strong>backpressure</strong> , precise signal semantics (onNext, onError, onComplete), and the dual function of the<strong>Flow.Processor</strong> create the basis for stable, extensible pipelines.</p><p>The patterns shown – from map and filter processors to batching and throttling – make it clear that robust processing chains can be built with little code. Supplemented by retry strategies, idempotency and interoperability with established reactive frameworks, a toolbox is created that is suitable for simple learning examples as well as for upscaled systems.</p><p>This makes it clear: If you work with event streams in Java, you can&rsquo;t get around the Flow API. It bridges the gap between classic streams, modern reactive programming and future developments such as virtual threads. It is crucial to consistently adhere to the principles of backpressure, clean use of resources and clear signal guidance. This results in pipelines that are not only functional, but also durable, high-performance and safe.</p>
]]></content:encoded><category>Design Pattern</category><category>Java</category><media:content url="https://svenruppert.com/images/2025/09/ChatGPT-Image-3.-Sept.-2025-13_49_18.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/09/ChatGPT-Image-3.-Sept.-2025-13_49_18.jpeg"/><enclosure url="https://svenruppert.com/images/2025/09/ChatGPT-Image-3.-Sept.-2025-13_49_18.jpeg" type="image/jpeg" length="0"/></item><item><title>Signal via SSE, data via REST – a Vaadin demonstration in Core Java</title><link>https://svenruppert.com/posts/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/</link><pubDate>Wed, 03 Sep 2025 13:05:56 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/</guid><description>1. Introduction 1.1 Motivation: Event-driven updating without polling In classic web applications, the pull principle still dominates: Clients repeatedly make requests to the server to detect changes. This polling is simple, but it leads to unnecessary load on the server and network side, especially if the data stock changes only sporadically. Server-Sent Events (SSE) is a standardised procedure that allows the server to signal changes to connected clients actively. This avoids unnecessary requests, while updates reach the interface promptly.</description><content:encoded>&lt;![CDATA[<h2 id="1-introduction">1. Introduction</h2><h3 id="11-motivation-event-driven-updating-without-polling">1.1 Motivation: Event-driven updating without polling</h3><p>In classic web applications, the pull principle still dominates: Clients repeatedly make requests to the server to detect changes. This polling is simple, but it leads to unnecessary load on the server and network side, especially if the data stock changes only sporadically.<em>Server-Sent Events (SSE)</em> is a standardised procedure that allows the server to signal changes to connected clients actively. This avoids unnecessary requests, while updates reach the interface promptly.</p><ol><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#1-introduction">1. Introduction</a><ol><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#1-1-motivation-event-driven-updating-without-polling">1.1 Motivation: Event-driven updating without polling</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#1-2-objectives-and-delimitation">1.2 Objectives and delimitation</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#1-3-overview-of-the-demonstration-scenario">1.3 Overview of the demonstration scenario</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#2-conceptual-foundations">2. Conceptual Foundations</a><ol><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#2-1-server-sent-events-sse">2.1 Server-Sent Events (SSE)</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#2-2-rest-as-a-transport-channel-for-payload-data">2.2 REST as a transport channel for payload data</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#2-3-vaadin-flow-server-side-ui-model-and-push">2.3 Vaadin Flow: Server-Side UI Model and Push</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#3-architecture-of-the-demonstrator">3. Architecture of the demonstrator</a><ol><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#3-1-component-overview">3.1 Component Overview</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#3-2-communication-relationships">3.2 Communication relationships</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#3-3-runtime-environment-and-assumptions">3.3 Runtime Environment and Assumptions</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#4-data-and-event-model">4. Data and event model</a><ol><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#4-1-record-structure">4.1 Record Structure</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#4-2-event-types-and-semantics">4.2 Event Types and Semantics</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#4-3-sequence-numbering-and-idempotent-reloading">4.3 Sequence numbering and idempotent reloading</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#5-rest-sse-server-with-in-process-cli">5. REST/SSE Server with In-Process CLI</a><ol><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#5-1-operating-concept-of-the-cli">5.1 Operating concept of the CLI</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#5-2-append-log-and-consistency-considerations">5.2 Append Log and Consistency Considerations</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#5-3-sse-broadcast-trigger-on-successful-input">5.3 SSE Broadcast: Trigger on Successful Input</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#5-4-optional-optimisations">5.4 Optional optimisations</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#6-vaadin-flow-integration-without-javascript">6. Vaadin Flow Integration (without JavaScript)</a><ol><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#6-1-server-side-sse-client">6.1 Server-Side SSE Client</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#6-2-ui-synchronisation-and-push">6.2 UI Synchronisation and Push</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#6-3-interaction-patterns">6.3 Interaction patterns</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#6-4-delta-retrieval-and-full-recall">6.4 Delta Retrieval and Full Recall</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#7-implementation-rest-sse-server">7. Implementation – REST SSE Server</a><ol><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#7-1-rest-server">7.1 REST Server</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#7-2-entry">7.2 Entry</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#7-3-ssehandler">7.3 SseHandler</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#7-4-datahandler">7.4 DataHandler</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#8-implementation-vaadin-flow-ui">8. Implementation – Vaadin Flow UI</a><ol><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#8-1-dashboardview">8.1 DashboardView</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#8-2-dataclient">8.2 DataClient</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#8-3-entry">8.3 Entry</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#8-4-sseclientservice">8.4 SseClientService</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#8-5-uibroadcaster">8.5 UiBroadcaster</a></li></ol></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#9-summary">9. Summary</a><ol><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#9-1-evaluation-of-the-signal-per-sse-data-per-rest-pattern">9.1 Evaluation of the &ldquo;Signal-per-SSE, Data-per-REST&rdquo; pattern</a></li><li><a href="https://svenruppert.com/2025/09/03/signal-via-sse-data-via-rest-a-vaadin-demonstration-in-core-java/#9-2-didactic-benefits-and-reusability">9.2 Didactic benefits and reusability</a></li></ol></li></ol><h3 id="12-objectives-and-delimitation">1.2 Objectives and delimitation</h3><p>This article aims to demonstrate the basic functionality of SSE in interaction with a Vaadin Flow application. The focus is on the separation of signalling and data retrieval: While SSE is used exclusively for notifications, the actual retrieval of the new data is done via REST endpoints. Security aspects such as authentication or authorisation, as well as persistent data storage, are deliberately excluded to emphasise the core idea clearly.</p><div class="pull-quote"><p><strong>The code is under the following URL on github:</strong></p><p><strong><a href="https://github.com/Java-Publications/Blog---Vaadin---How-to-consume-SSE-from-a-REST-Service">https://github.com/Java-Publications/Blog---Vaadin---How-to-consume-SSE-from-a-REST-Service</a></strong></p></div><h3 id="13-overview-of-the-demonstration-scenario">1.3 Overview of the demonstration scenario</h3><p>The demonstrator consists of three components:</p><ul><li><strong>REST/SSE server with CLI input</strong> : New data is entered directly from the console during runtime and stored in simple in-memory storage. Each input immediately triggers an SSE signal.</li><li><strong>SSE signal</strong> : The server sends an event after each input that tells the clients that new data is available.</li><li><strong>Vaadin Flow application</strong> : The UI registers as an SSE client, displays notifications about new data and allows users to reload and display them in a targeted manner via REST retrieval.</li></ul><p>This constellation provides a clear and comprehensible example of integrating SSE into a server-side Java UI framework. The separation between signal and data allows for a robust and easy-to-understand architecture that is suitable for a wide range of real-time scenarios.</p><h2 id="2-conceptual-foundations">2. Conceptual Foundations</h2><h3 id="21-server-sent-events-sse">2.1 Server-Sent Events (SSE)</h3><p>Server-sent events (SSE) are a standardised procedure for continuously transmitting messages from the server to the client. Communication is unidirectional: the server sends, the client receives. Technically, SSE is based on a persistent HTTP connection with the MIME type<em>text/event-stream</em>. Messages consist of simple lines of text and are interpreted by the browser or a client framework.</p><p>The advantages of SSE include the simplicity of implementation, automatic client-side reconnect, and good integration into existing HTTP infrastructures. Limitations result from the restriction to UTF-8 text and the lack of possibility of bidirectional communication. However, for pure notifications and status messages, SSE is usually more efficient and robust than more complex alternatives such as WebSockets or Long Polling.</p><h3 id="22-rest-as-a-transport-channel-for-payload-data">2.2 REST as a transport channel for payload data</h3><p>REST-based endpoints have established themselves as the standard for transmitting structured or binary data. They are stateless, easily scalable and benefit from established infrastructure such as caching and monitoring. In this demonstration scenario, REST is used to provide the actual data that the client explicitly requests after a notification via SSE. This separation ensures clear responsibilities: SSE signals that something has changed; REST offers the content.</p><h3 id="23-vaadin-flow-server-side-ui-model-and-push">2.3 Vaadin Flow: Server-Side UI Model and Push</h3><p>Vaadin Flow is a server-side Java framework for web application development. The UI logic runs on the server, while the browser only takes care of the display. Changes in server state are transmitted to the client via a synchronised communication channel. For the integration of SSE, it is particularly relevant that Vaadin works with<em>push</em> : Server-side events can be transferred directly to the interface. This allows the reception of SSE signals to be elegantly combined with UI updates without the need for client-side JavaScript.</p><h2 id="3-architecture-of-the-demonstrator">3. Architecture of the demonstrator</h2><h3 id="31-component-overview">3.1 Component Overview</h3><p>The demonstrator consists of three main components. The first component is the<strong>REST/SSE server</strong>. It is responsible for providing an endpoint for entering new data, storing this data in memory and simultaneously sending signals to the connected clients via SSE as soon as the data stock changes. In addition, it provides REST endpoints through which the clients can retrieve the current data.</p><p>The second component is the<strong>in-process CLI</strong> , which is embedded directly in the server process. It enables manual entry of new data rows via the console during runtime. Each input is immediately stored as a data record in the in-memory. At the same time, this process triggers an event that is forwarded to the connected clients via SSE.</p><p>The third component is the<strong>Vaadin Flow application</strong>. It acts as a client that receives the signals sent via SSE and informs users that new data is available. If desired, the application can load this data specifically via the provided REST endpoints and display it directly in the user interface. This covers the entire process from input to signalling to visibility in the UI.</p><h3 id="32-communication-relationships">3.2 Communication relationships</h3><p>The architecture follows the pattern<strong>signal via SSE, data via REST.</strong> As soon as the CLI receives a new input, the server stores the entry in memory and sends a corresponding SSE update to all connected clients. The Vaadin application receives this signal, informs the user of the availability of new data, and provides an option for targeted retrieval. Via a subsequent REST request, the Vaadin application requests the data and updates the user interface.</p><h3 id="33-runtime-environment-and-assumptions">3.3 Runtime Environment and Assumptions</h3><p>Some deliberately simplified framework conditions apply to the demonstration. The server and Vaadin application run locally on the same host, but in separate processes and typically on different ports, such as 8080 for the server and 8081 for the UI. This simulates a realistic separation of the systems without the need for a complex infrastructure.</p><p>In addition, the CORS policy is set in such a way that access is possible without restriction. This open configuration is only for the sake of simplifying the demonstration, as security considerations are not taken into account in this scenario.</p><p>The data is stored exclusively in the working memory. Each record entered remains available only during runtime and is lost after the process ends. Persistence mechanisms such as databases or file systems are deliberately not used in this example to focus entirely on the interaction of CLI input, SSE signal and REST retrieval.</p><p>This architecture is deliberately kept simple to demonstrate the functionality of SSE in combination with REST and Vaadin Flow in a clear and comprehensible way.</p><h2 id="4-data-and-event-model">4. Data and event model</h2><h3 id="41-record-structure">4.1 Record Structure</h3><p>In the demonstration scenario, new data is generated by entering lines of text in the CLI. Each entry is stored in the server in the form of a simple data record. This consists of a consecutive sequence number that is incremented with each input, a timestamp that documents the time of entry, and the actual content, i.e. the text entered by the operator. It is implicitly assumed that both the REST/SSE server and the Vaadin application are based on the same system time. This is the only way to compare the timestamps directly without the need for additional synchronisation mechanisms. This structure is deliberately kept minimalist to ensure traceability and to focus on the interaction of the components.</p><h3 id="42-event-types-and-semantics">4.2 Event Types and Semantics</h3><p>Communication between server and client takes place via events that are transmitted in SSE format. The focus is on the<em>update</em> event, which is always sent when a new record is added. At a minimum, the current sequence number is transmitted as the data load of this event, so that the client can detect whether there are any new entries for it. Optionally, an<em>init</em> event could also be used, which signals the start state when a new connection is established. Other event types, such as reset or snapshot, can be provided for advanced scenarios, but are not required for demonstration.</p><h3 id="43-sequence-numbering-and-idempotent-reloading">4.3 Sequence numbering and idempotent reloading</h3><p>The sequence number plays a central role in the consistency between server and client. It enables the retrieval of only the data records added since the last known number via REST. This allows clients to remain correctly synchronised even if they have missed one or more SSE signals. This makes the reload idempotent: a new call with the same<em>since</em> specification always returns the same result set, regardless of how often it is repeated. This principle facilitates fault tolerance and ensures a stable processing chain.</p><h2 id="5-restsse-server-with-in-process-cli">5. REST/SSE Server with In-Process CLI</h2><h3 id="51-operating-concept-of-the-cli">5.1 Operating concept of the CLI</h3><p>The server process has an integrated command line interface that can be used to enter new records during runtime. Each input corresponds to a single line of text that is taken directly into the in-memory. This simple operating concept allows the data source to be controlled manually and thus trigger targeted events, which are then signalled to the clients via SSE.</p><h3 id="52-append-log-and-consistency-considerations">5.2 Append Log and Consistency Considerations</h3><p>The entries are stored in the form of an append log: Each new record is added to the end of the existing list. The sequential sequence number ensures that the sequence can be clearly determined. This simple structure ensures that all clients can consistently reload the same state when needed. More complex mechanisms, such as transactions or locks, are not required for demonstration.</p><h3 id="53-sse-broadcast-trigger-on-successful-input">5.3 SSE Broadcast: Trigger on Successful Input</h3><p>As soon as a new record is added to memory, the server immediately generates an SSE event of type<em>update</em>. This event is sent out to all connected clients and contains at least the current sequence number. This enables all registered recipients to recognise that new data is available. The actual content is not transmitted, but is reserved for later retrieval via REST.</p><h3 id="54-optional-optimisations">5.4 Optional optimisations</h3><p>For a demonstration scenario, it is sufficient to forward each new event immediately. However, in more realistic environments, optimisations can be helpful. This includes, for example, combining several fast inputs into a combined update signal to reduce the number of messages transmitted. Similarly, regular ping comments can be used to stabilise the connection and ensure that inactive clients are detected and removed. These measures increase robustness, but are not necessary for functional demonstration. We will explore these concepts further in another article, specifically when we discuss the open-source URL shortener project.</p><h2 id="6-vaadin-flow-integration-without-javascript">6. Vaadin Flow Integration (without JavaScript)</h2><h3 id="61-server-side-sse-client">6.1 Server-Side SSE Client</h3><p>The Vaadin Flow application operates a server-side SSE client that is permanently connected to the SSE endpoint of the REST server. This allows the application to receive signals independently of client-side JavaScript and process them directly on the server side. If a connection is interrupted, a reconnect attempt is automatically started so that the UI remains continuously informed about the current status.</p><h3 id="62-ui-synchronisation-and-push">6.2 UI Synchronisation and Push</h3><p>Since Vaadin Flow works on the principle of a server-side UI model, external events must be integrated into the UI thread. This is done via synchronised accesses, which ensure that changes are executed consistently and thread-safe. In combination with Vaadin Push, incoming SSE signals can be converted directly into visible updates. Users notice the update immediately, without the need for a manual refresh. We deliberately disregard the special case of a complete page reload at this point.</p><h3 id="63-interaction-patterns">6.3 Interaction patterns</h3><p>The Vaadin interface is designed to only display a hint at first, rather than automatically loading all new data upon arrival of an incoming SSE signal. This can be done in the form of a notification or by activating a button. Only through a conscious action by the user is the new data retrieved via REST and integrated into the interface. In addition, the user can selectively decide which detailed data is of interest. This interaction pattern illustrates the separation of signalling and data retrieval and makes the functionality particularly clear for demonstration purposes.</p><h3 id="64-delta-retrieval-and-full-recall">6.4 Delta Retrieval and Full Recall</h3><p>When reloading the data via REST, a distinction can be made between two modes: A complete retrieval always loads the entire data set. In contrast, a delta retrieval retrieves only those entries that have been added since the last known sequence number. If no sequence number is available, the entire database is not transferred, but only the last n messages are loaded. This procedure is sufficient for the demonstration, as it facilitates traceability. In scenarios closer to production, delta retrieval still offers advantages in terms of efficiency and bandwidth.</p><h2 id="7-implementation--rest-sse-server">7. Implementation – REST SSE Server</h2><h3 id="71-rest-server">7.1 REST Server</h3><p>The<code>RestServer</code> bundles the entire server functionality based on<code>com.sun.net.httpserver.HttpServer</code>. It initialises the three endpoints of the demonstrator (<code>/sse</code>,<code>/data</code>,<code>/health</code>) and manages the server resources required for SSE. These include the number of currently connected output streams (sseClients), an in-memory append log of the data records, and the execution services required at runtime (a Scheduled Executor for Keep pings and a Connection Executor for long-lived SSE connections).</p><p>During operation, the server starts a CLIThread that receives rows from<code>stdin</code> and inserts them into the log as new records. Each entry is given a monotonic sequence number and a time stamp; an SSEUpdate is then sent to all connected clients. The<code>RestServer</code> also provides helper functions for CORSHeader, standardised answers, query parsing, and the output of the SSE form. The since<code>(long)</code> and<code>lastN(int)</code> methods map the REST reading paths: They either return all entries after a specific sequence number or the last<em>n</em> entries for initial synchronisation if no sequence is yet known.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.rest;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public class RestServer {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public static final int DEFAULT_LAST_N = 20;</span></span><span class="line"><span class="cl">  public static final String PATH_SSE = "/sse";</span></span><span class="line"><span class="cl">  public static final String PATH_DATA = "/data";</span></span><span class="line"><span class="cl">  public static final String PATH_HEALTH = "/health";</span></span><span class="line"><span class="cl">  public static final String CONTENT_TYPE = "text/plain; charset=utf-8";</span></span><span class="line"><span class="cl">  public static final int PORT = 8080;</span></span><span class="line"><span class="cl">  public static final long PING_INTERVAL_MILLIS = 30_000L;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  protected static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private final HttpServer http;</span></span><span class="line"><span class="cl">  private final Set<span class="nt">&lt;OutputStream&gt;</span> sseClients = ConcurrentHashMap.newKeySet();</span></span><span class="line"><span class="cl">  private final CopyOnWriteArrayList<span class="nt">&lt;Entry&gt;</span> store = new CopyOnWriteArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">  private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);</span></span><span class="line"><span class="cl">  private final ExecutorService connectionExecutor = Executors.newCachedThreadPool();</span></span><span class="line"><span class="cl">  private final AtomicLong seq = new AtomicLong(0);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public RestServer(int port)</span></span><span class="line"><span class="cl">      throws IOException {</span></span><span class="line"><span class="cl">    http = HttpServer.create(new InetSocketAddress(port), 0);</span></span><span class="line"><span class="cl">    http.createContext(PATH_SSE, new SseHandler(this));</span></span><span class="line"><span class="cl">    http.createContext(PATH_DATA, new DataHandler(this));</span></span><span class="line"><span class="cl">    http.createContext(PATH_HEALTH, ex -&gt; respond(ex, 200, "OK"));</span></span><span class="line"><span class="cl">    http.setExecutor(connectionExecutor);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  ---Main---</span></span><span class="line"><span class="cl">  public static void main(String[] args)</span></span><span class="line"><span class="cl">      throws Exception {</span></span><span class="line"><span class="cl">    RestServer srv = new RestServer(PORT);</span></span><span class="line"><span class="cl">    Runtime.getRuntime().addShutdownHook(new Thread(srv::stop));</span></span><span class="line"><span class="cl">    srv.start();</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public AtomicLong getSeq() {</span></span><span class="line"><span class="cl">    return seq;</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public Set<span class="nt">&lt;OutputStream&gt;</span> getSseClients() {</span></span><span class="line"><span class="cl">    return sseClients;</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public ScheduledExecutorService getScheduler() {</span></span><span class="line"><span class="cl">    return scheduler;</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public ExecutorService getConnectionExecutor() {</span></span><span class="line"><span class="cl">    return connectionExecutor;</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public void writeEvent(OutputStream os, String event, String data)</span></span><span class="line"><span class="cl">      throws IOException {</span></span><span class="line"><span class="cl">    os.write(sseFormat(event, data));</span></span><span class="line"><span class="cl">    os.flush();</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public void writeComment(OutputStream os, String comment)</span></span><span class="line"><span class="cl">      throws IOException {</span></span><span class="line"><span class="cl">    os.write(("# " + comment + "\n\n").getBytes(StandardCharsets.UTF_8));</span></span><span class="line"><span class="cl">    os.flush();</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public byte[] sseFormat(String event, String data) {</span></span><span class="line"><span class="cl">    String msg = "event: " + event + "\n" + "data: " + data + "\n\n";</span></span><span class="line"><span class="cl">    return msg.getBytes(StandardCharsets.UTF_8);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  --- Utils ---</span></span><span class="line"><span class="cl">  public void addCors(HttpExchange ex) { ex.getResponseHeaders().add(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); }</span></span><span class="line"><span class="cl">  public void respond(HttpExchange ex, int code, String body)</span></span><span class="line"><span class="cl">      throws IOException { respond(ex, code, body, CONTENT_TYPE); }</span></span><span class="line"><span class="cl">  public void respond(HttpExchange ex, int code, String body, String contentType)</span></span><span class="line"><span class="cl">      throws IOException {</span></span><span class="line"><span class="cl">    Headers h = ex.getResponseHeaders();</span></span><span class="line"><span class="cl">    h.add("Content-Type", contentType);</span></span><span class="line"><span class="cl">    byte[] bytes = body.getBytes(StandardCharsets.UTF_8);</span></span><span class="line"><span class="cl">    ex.sendResponseHeaders(code, bytes.length);</span></span><span class="line"><span class="cl">    try (OutputStream os = ex.getResponseBody()) {</span></span><span class="line"><span class="cl">      os.write(bytes);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public Map<span class="nt">&lt;String</span><span class="err">,</span><span class="err">List&lt;String</span><span class="nt">&gt;</span>&gt; parseQuery(URI uri) {</span></span><span class="line"><span class="cl">    Map<span class="nt">&lt;String</span><span class="err">,</span><span class="err">List&lt;String</span><span class="nt">&gt;</span>&gt; map = new LinkedHashMap<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">    String query = uri.getRawQuery();</span></span><span class="line"><span class="cl">    if (query == null || query.isEmpty()) return map;</span></span><span class="line"><span class="cl">    for (String pair : query.split("<span class="err">&amp;</span>")) {</span></span><span class="line"><span class="cl">      int idx = pair.indexOf('=');</span></span><span class="line"><span class="cl">      String key = idx &gt; 0 ? decode(pair.substring(0, idx)) : decode(pair);</span></span><span class="line"><span class="cl">      String val = idx &gt; 0<span class="err">&amp;&amp;</span> pair.length() &gt; idx + 1 ? decode(pair.substring(idx + 1)) : "";</span></span><span class="line"><span class="cl">      map.computeIfAbsent(key, k -&gt; new ArrayList<span class="err">&lt;</span>&gt;()).add(val);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    return map;</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public String decode(Strings) { return URLDecoder.decode(s, StandardCharsets.UTF_8); }</span></span><span class="line"><span class="cl">  public String first(List<span class="nt">&lt;String&gt;</span> list) { return (list == null || list.isEmpty()) ? null : list.get(0); }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  --- Start / Stop ---</span></span><span class="line"><span class="cl">  public void start() {</span></span><span class="line"><span class="cl">    http.start();</span></span><span class="line"><span class="cl">    System.out.println("REST/SSE server running on http://localhost:" + PORT);</span></span><span class="line"><span class="cl">    CLI thread for in-process input</span></span><span class="line"><span class="cl">    Thread cli = new Thread(this::cliLoop, "cli-loop");</span></span><span class="line"><span class="cl">    cli.setDaemon(true);</span></span><span class="line"><span class="cl">    cli.start();</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public void stop() {</span></span><span class="line"><span class="cl">    try {</span></span><span class="line"><span class="cl">      http.stop(0);</span></span><span class="line"><span class="cl">    } catch (Exception ignored) {</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    try {</span></span><span class="line"><span class="cl">      scheduler.shutdownNow();</span></span><span class="line"><span class="cl">    } catch (Exception ignored) {</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    try {</span></span><span class="line"><span class="cl">      connectionExecutor.shutdownNow();</span></span><span class="line"><span class="cl">    } catch (Exception ignored) {</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    for (OutputStream os : sseClients) {</span></span><span class="line"><span class="cl">      try {</span></span><span class="line"><span class="cl">        os.close();</span></span><span class="line"><span class="cl">      } catch (IOException ignored) {</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    sseClients.clear();</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  ---CLI---</span></span><span class="line"><span class="cl">  protected void cliLoop() {</span></span><span class="line"><span class="cl">    System.out.println("CLI ready. Type in lines of text. 'exit' terminates the server.");</span></span><span class="line"><span class="cl">    try (BufferedReader br = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) {</span></span><span class="line"><span class="cl">      String line;</span></span><span class="line"><span class="cl">      while ((line = br.readLine()) != null) {</span></span><span class="line"><span class="cl">        if (line.equalsIgnoreCase("exit")) {</span></span><span class="line"><span class="cl">          System.out.println("End server...");</span></span><span class="line"><span class="cl">          stop();</span></span><span class="line"><span class="cl">          break;</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        if (line.isBlank()) {</span></span><span class="line"><span class="cl">          System.out.println("(blank line ignored)");</span></span><span class="line"><span class="cl">          continue;</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        appendNew(line);</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    } catch (IOException e) {</span></span><span class="line"><span class="cl">      System.err.println("CLI exited: " + e.getMessage());</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private void appendNew(String text) {</span></span><span class="line"><span class="cl">    long n = seq.incrementAndGet();</span></span><span class="line"><span class="cl">    Entry e = new Entry(n, Instant.now(), text);</span></span><span class="line"><span class="cl">    store.add(e);</span></span><span class="line"><span class="cl">    System.out.println("[APPEND] " + e);</span></span><span class="line"><span class="cl">    broadcastUpdate(s);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private void broadcastUpdate(long highestSeq) {</span></span><span class="line"><span class="cl">    String payload = Long.toString(highestSeq);</span></span><span class="line"><span class="cl">    byte[] bytes = sseFormat("update", payload);</span></span><span class="line"><span class="cl">    List<span class="nt">&lt;OutputStream&gt;</span> dead = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">    for (OutputStream os : sseClients) {</span></span><span class="line"><span class="cl">      try {</span></span><span class="line"><span class="cl">        os.write(bytes);</span></span><span class="line"><span class="cl">        os.flush();</span></span><span class="line"><span class="cl">      } catch (IOException e) {</span></span><span class="line"><span class="cl">        dead.add(os);</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    if (!dead.isEmpty()) sseClients.removeAll(dead);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public List<span class="nt">&lt;Entry&gt;</span> since(long s) {</span></span><span class="line"><span class="cl">    List<span class="nt">&lt;Entry&gt;</span> out = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">    for (Entry e : store) if (e.seq() &gt; s) out.add(e);</span></span><span class="line"><span class="cl">    return out;</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public List<span class="nt">&lt;Entry&gt;</span> lastN(int n) {</span></span><span class="line"><span class="cl">    int size = store.size();</span></span><span class="line"><span class="cl">    if (n<span class="err">&lt;</span>= 0) return List.of();</span></span><span class="line"><span class="cl">    int from = Math.max(0, size - n);</span></span><span class="line"><span class="cl">    return new ArrayList<span class="err">&lt;</span>&gt;(store.subList(from, size));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h3 id="72-entry">7.2 Entry</h3><p><code>Entry</code>models a single record as a record with the fields<code>seq</code> (sequence number),<code>ts</code> (timestamp) and<code>text</code> (payload from the CLI). The record is immutable and therefore well-suited for concurrent read accesses. The<code>toString()</code>representation is deliberately kept simple and outputs the three fields in one line; it is used for text-based transmission via the /data endpoint and supports simple parsing on the client side.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.svenruppert.rest</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="o">---</span><span class="n">Model</span><span class="o">---</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">record</span><span class="nc">Entry</span><span class="p">(</span><span class="kt">long</span><span class="w"/><span class="n">seq</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">ts</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">text</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nd">@NotNull</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">seq</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"|"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">DateTimeFormatter</span><span class="p">.</span><span class="na">ISO_INSTANT</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="n">ts</span><span class="p">)</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"|"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">text</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="73-ssehandler">7.3 SseHandler</h3><p>The<code>SseHandler</code> implements the SSE endpoint<code>/sse</code>. When called, it sets up the HTTP response for a<em>text/event stream</em> , inserts the client&rsquo;s output stream into the managed set of SSE connections, and sends an initial event to confirm the successful connection. A periodic Keep‑Alive mechanism (Ping Comments) keeps the connection open and supports the detection of aborted clients. The handler works closely with the helper functions provided by the<code>RestServer</code> (writing individual events or comments, accessing schedulers, and connection management).</p><p>If there is a write error or the remote station is closed, the handler removes the connection from the set of active streams and cleans up associated resources. In combination with the server&rsquo;s broadcasting, this creates a resilient but straightforward push model for signals via newly available data.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.svenruppert.rest.handler</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="o">---</span><span class="n">SIZE</span><span class="o">---</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">record</span><span class="nc">SseHandler</span><span class="p">(</span><span class="n">RestServer</span><span class="w"/><span class="n">restServer</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">implements</span><span class="w"/><span class="n">HttpHandler</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handle</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">ex</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">" GET"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="na">getRequestMethod</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">restServer</span><span class="p">.</span><span class="na">respond</span><span class="p">(</span><span class="n">ex</span><span class="p">,</span><span class="w"/><span class="n">405</span><span class="p">,</span><span class="w"/><span class="s">"Method Not Allowed"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">restServer</span><span class="p">.</span><span class="na">addCors</span><span class="p">(</span><span class="n">ex</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">Headers</span><span class="w"/><span class="n">h</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">getResponseHeaders</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">h</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span><span class="w"/><span class="s">"text/event-stream; charset=utf-8"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">h</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">"Cache-Control"</span><span class="p">,</span><span class="w"/><span class="s">"no-cache"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">h</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="s">"Connection"</span><span class="p">,</span><span class="w"/><span class="s">"keep-alive"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">ex</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">200</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">final</span><span class="w"/><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ex</span><span class="p">.</span><span class="na">getResponseBody</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">restServer</span><span class="p">.</span><span class="na">getSseClients</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="n">os</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">Initial</span><span class="w"/><span class="n">Event</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">restServer</span><span class="p">.</span><span class="na">writeEvent</span><span class="p">(</span><span class="n">os</span><span class="p">,</span><span class="w"/><span class="s">"init"</span><span class="p">,</span><span class="w"/><span class="s">"ready"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">// Keep-Alive Pings</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">ScheduledFuture</span><span class="o">&lt;?&gt;</span><span class="w"/><span class="n">pinger</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">restServer</span><span class="p">.</span><span class="na">getScheduler</span><span class="p">().</span><span class="na">scheduleAtFixedRate</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">restServer</span><span class="p">.</span><span class="na">writeComment</span><span class="p">(</span><span class="n">os</span><span class="p">,</span><span class="w"/><span class="s">"ping"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="cm">/* will be closed below */</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">},</span><span class="w"/><span class="n">RestServer</span><span class="p">.</span><span class="na">PING_INTERVAL_MILLIS</span><span class="p">,</span><span class="w"/><span class="n">RestServer</span><span class="p">.</span><span class="na">PING_INTERVAL_MILLIS</span><span class="p">,</span><span class="w"/><span class="n">TimeUnit</span><span class="p">.</span><span class="na">MILLISECONDS</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">//Keep blocking open until client/OS closes</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">restServer</span><span class="p">.</span><span class="na">getConnectionExecutor</span><span class="p">().</span><span class="na">execute</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">os</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="c1">//NOP: we only write at events, otherwise Ping will keep the line open</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="c1">//Wait for an exception to occur/OS to close</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="c1">//(an explicit read/write loop is not necessary here)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">join</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">InterruptedException</span><span class="w"/><span class="n">ignored</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">ignored</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">pinger</span><span class="p">.</span><span class="na">cancel</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">restServer</span><span class="p">.</span><span class="na">getSseClients</span><span class="p">().</span><span class="na">remove</span><span class="p">(</span><span class="n">os</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">os</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">ignored</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="74-datahandler">7.4 DataHandler</h3><p>The<code>DataHandler</code> provides the REST endpoint<code>/data</code> and implements the two intended read variants. Depending o sequence number is known,on the Query parameters, it returns all entries with a sequence number greater than a given the last<em>n</em> entries by default (configurable via<code>DEFAULT_LAST_N</code> or the<code>lastN parameter</code>). The response is generated as<code>text/plain</code>, with each record output on its own line.</p><p>The handler also validates the QueryParameters and produces consistent error messages for invalid inputs. Together with the<code>SseHandler</code>, it maps the separation of signaling (SSE) and payload retrieval (REST) and allows a deterministic, idempotent reload process on the client side.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.rest.handler;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">---DATA---</span></span><span class="line"><span class="cl">public record DataHandler(RestServer restServer)</span></span><span class="line"><span class="cl">    implements HttpHandler {</span></span><span class="line"><span class="cl">  @Override</span></span><span class="line"><span class="cl">  public void handle(HttpExchange ex)</span></span><span class="line"><span class="cl">      throws IOException {</span></span><span class="line"><span class="cl">    if (!" GET".equals(ex.getRequestMethod())) {</span></span><span class="line"><span class="cl">      restServer.respond(ex, 405, "Method Not Allowed");</span></span><span class="line"><span class="cl">      return;</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    restServer.addCors(ex);</span></span><span class="line"><span class="cl">    Map<span class="nt">&lt;String</span><span class="err">,</span><span class="err">List&lt;String</span><span class="nt">&gt;</span>&gt; q = restServer.parseQuery(ex.getRequestURI());</span></span><span class="line"><span class="cl">    String sinceStr = restServer.first(q.get("since"));</span></span><span class="line"><span class="cl">    String lastNStr = restServer.first(q.get("lastN"));</span></span><span class="line"><span class="cl">    List<span class="nt">&lt;Entry&gt;</span> result;</span></span><span class="line"><span class="cl">    if (sinceStr != null<span class="err">&amp;&amp;</span> !sinceStr.isBlank()) {</span></span><span class="line"><span class="cl">      long s;</span></span><span class="line"><span class="cl">      try {</span></span><span class="line"><span class="cl">        s = Long.parseLong(sinceStr);</span></span><span class="line"><span class="cl">      } catch(NumberFormatException nfe) {</span></span><span class="line"><span class="cl">        restServer.respond(ex, 400, "since must be a number");</span></span><span class="line"><span class="cl">        return;</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">      result = restServer.since(s);</span></span><span class="line"><span class="cl">    } else {</span></span><span class="line"><span class="cl">      int n = RestServer.DEFAULT_LAST_N;</span></span><span class="line"><span class="cl">      if (lastNStr != null<span class="err">&amp;&amp;</span> !lastNStr.isBlank()) {</span></span><span class="line"><span class="cl">        try {</span></span><span class="line"><span class="cl">          n = Integer.parseInt(lastNStr);</span></span><span class="line"><span class="cl">        } catch(NumberFormatException nfe) {</span></span><span class="line"><span class="cl">          restServer.respond(ex, 400, "lastN must be a number");</span></span><span class="line"><span class="cl">          return;</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">      result = restServer.lastN(n);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    Output as text/plain, one line per entry: seq|ISO-TS|text</span></span><span class="line"><span class="cl">    StringBuilder sb = new StringBuilder();</span></span><span class="line"><span class="cl">    for (Entry e : result) {</span></span><span class="line"><span class="cl">      sb.append(e.toString()).append('\n');</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    restServer.respond(ex, 200, sb.toString(), RestServer.CONTENT_TYPE);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h2 id="8-implementation--vaadin-flow-ui">8. Implementation – Vaadin Flow UI</h2><h3 id="81-dashboardview">8.1 DashboardView</h3><p>The<code>DashboardView</code>is the central view of the demonstration and is integrated via the route<code>dashboard</code>. It presents the current data in a<code>grid</code> and provides the option to reload with a button. When attached, the view registers with the<code>UiBroadcaster</code> and binds a listener to the<code>SseClientService</code>. If an &ldquo;update&rdquo; signal arrives, the view informs the users (status bar, notification) and activates the reload button. The actual retrieval only takes place after user action: Either new entries are loaded since the last known sequence (<code>lastSeq</code>) or, if no sequence is yet available, the last<em>n</em> messages. After successful retrieval,<code>lastSeq</code>is updated, and the grid is updated consistently. With the detach, the view moves away from the broadcaster and deregisters the listener.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.flow.views.dashboard;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">@Route(value = DashboardView.ROUTE, layout = MainLayout.class)</span></span><span class="line"><span class="cl">public class DashboardView</span></span><span class="line"><span class="cl">    extends Composite<span class="nt">&lt;VerticalLayout&gt;</span></span></span><span class="line"><span class="cl">    implements HasLogger {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public static final String ROUTE = "dashboard";</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  ---Configuration---</span></span><span class="line"><span class="cl">  private static final String BASE = "http://localhost:8090"; Server Base</span></span><span class="line"><span class="cl">  private static final String SSE_URL = BASE + "/sse";</span></span><span class="line"><span class="cl">  private static final String DATA_URL = BASE + "/data";</span></span><span class="line"><span class="cl">  private static final int DEFAULT_LAST_N = 20;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  ---UI---</span></span><span class="line"><span class="cl">  private final Grid<span class="nt">&lt;Entry&gt;</span> grid = new Grid<span class="err">&lt;</span>&gt;(Entry.class, false);</span></span><span class="line"><span class="cl">  private final Button fetchBtn = new Button("fetch data");</span></span><span class="line"><span class="cl">  private final Span status = new Span("Waiting for events ...");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  --- client services ---</span></span><span class="line"><span class="cl">  private final DataClient dataClient = new DataClient(DATA_URL);</span></span><span class="line"><span class="cl">  private final SseClientService sseClient = new SseClientService(SSE_URL);</span></span><span class="line"><span class="cl">  private final AtomicBoolean hasNew = new AtomicBoolean(false);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  ---Condition---</span></span><span class="line"><span class="cl">  private volatile Long lastSeq = zero; last confirmed sequence</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public DashboardView() {</span></span><span class="line"><span class="cl">    getContent().setSizeFull();</span></span><span class="line"><span class="cl">    grid.addColumn(Entry::seq).setHeader("Seq").setAutoWidth(true).setFlexGrow(0);</span></span><span class="line"><span class="cl">    grid.addColumn(e -&gt; e.ts().toString()).setHeader("Timestamp").setAutoWidth(true).setFlexGrow(0);</span></span><span class="line"><span class="cl">    grid.addColumn(Entry::text).setHeader("Text").setFlexGrow(1);</span></span><span class="line"><span class="cl">    fetchBtn.setEnabled(false);</span></span><span class="line"><span class="cl">    fetchBtn.addClickListener(e -&gt; fetch());</span></span><span class="line"><span class="cl">    getContent().add(status, fetchBtn, grid);</span></span><span class="line"><span class="cl">    addAttachListener(ev -&gt; {</span></span><span class="line"><span class="cl">      UI ui = ev.getUI();</span></span><span class="line"><span class="cl">      UiBroadcaster.register(ui);</span></span><span class="line"><span class="cl">      SseClientService.Listener l = (type, data) -&gt; {</span></span><span class="line"><span class="cl">        if ("update".equals(type)) {</span></span><span class="line"><span class="cl">          try {</span></span><span class="line"><span class="cl">            long seq = Long.parseLong(data.trim());</span></span><span class="line"><span class="cl">            mark only; Fetch decides on Delta/lastN</span></span><span class="line"><span class="cl">            hasNew.set(true);</span></span><span class="line"><span class="cl">            UiBroadcaster.broadcast(() -&gt; {</span></span><span class="line"><span class="cl">              fetchBtn.setEnabled(true);</span></span><span class="line"><span class="cl">              status.setText("New data available (seq=" + seq + ") – please retrieve.");</span></span><span class="line"><span class="cl">              Notification.show("New data available", 1200, Notification.Position.TOP_CENTER);</span></span><span class="line"><span class="cl">            });</span></span><span class="line"><span class="cl">          } catch (NumberFormatException ignore) {</span></span><span class="line"><span class="cl">            Fallback: Hint without seq</span></span><span class="line"><span class="cl">            hasNew.set(true);</span></span><span class="line"><span class="cl">            UiBroadcaster.broadcast(() -&gt; {</span></span><span class="line"><span class="cl">              fetchBtn.setEnabled(true);</span></span><span class="line"><span class="cl">              status.setText("New data available – please retrieve.");</span></span><span class="line"><span class="cl">            });</span></span><span class="line"><span class="cl">          }</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">      };</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">      sseClient.addListener(l);</span></span><span class="line"><span class="cl">      addDetachListener(ev2 -&gt; {</span></span><span class="line"><span class="cl">        sseClient.removeListener(l);</span></span><span class="line"><span class="cl">        UiBroadcaster.unregister(ui);</span></span><span class="line"><span class="cl">      });</span></span><span class="line"><span class="cl">    });</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private void fetch() {</span></span><span class="line"><span class="cl">    fetchBtn.setEnabled(false);</span></span><span class="line"><span class="cl">    List<span class="nt">&lt;Entry&gt;</span> toShow;</span></span><span class="line"><span class="cl">    if (lastSeq != null) {</span></span><span class="line"><span class="cl">      toShow = dataClient.fetchSince(lastSeq);</span></span><span class="line"><span class="cl">    } else {</span></span><span class="line"><span class="cl">      toShow = dataClient.fetchLastN(DEFAULT_LAST_N);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    if (!toShow.isEmpty()) {</span></span><span class="line"><span class="cl">      lastSeq = toShow.getLast().seq(); Latest Bookmark</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    UI ui = UI.getCurrent();</span></span><span class="line"><span class="cl">    if (ui != null) {</span></span><span class="line"><span class="cl">      effectively final</span></span><span class="line"><span class="cl">      ui.access(() -&gt; {</span></span><span class="line"><span class="cl">        if (!toShow.isEmpty()) {</span></span><span class="line"><span class="cl">          List<span class="nt">&lt;Entry&gt;</span> current = new ArrayList<span class="err">&lt;</span>&gt;(grid.getListDataView().getItems().toList());</span></span><span class="line"><span class="cl">          current.addAll(toShow);</span></span><span class="line"><span class="cl">          grid.setItems(current);</span></span><span class="line"><span class="cl">          status.setText("Data loaded(" + current.size() + " entries).");</span></span><span class="line"><span class="cl">        } else {</span></span><span class="line"><span class="cl">          status.setText("No new entries.");</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        hasNew.set(false);</span></span><span class="line"><span class="cl">      });</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h3 id="82-dataclient">8.2 DataClient</h3><p>The<code>DataClient</code>encapsulates the REST‑communication with the /data endpoint. It offers two read paths:<code>fetchSince(long since)</code> for delta fetches and fetchLastN(int n) for initial synchronisation. The answers are expected as<code>text/plain</code>and converted to a list of<code>Entry</code>(format:<code>seq|ISOTS|text</code>, one line per record). By encapsulating the HTTPDetails, the view remains slim; Changes to the transport format or timeouts can be adjusted centrally without touching the UICode.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.flow.views.dashboard;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">--- REST client for /data ---</span></span><span class="line"><span class="cl">public final class DataClient {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private final HttpClient http = HttpClient.newBuilder()</span></span><span class="line"><span class="cl">      .connectTimeout(Duration.ofSeconds(5)).build();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private final String baseUrl;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public DataClient(String baseUrl) {</span></span><span class="line"><span class="cl">    this.baseUrl = baseUrl;</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  //Server format: seq|ISO-TS|text per line</span></span><span class="line"><span class="cl">  private static List<span class="nt">&lt;Entry&gt;</span> parse(String body)</span></span><span class="line"><span class="cl">      throws IOException {</span></span><span class="line"><span class="cl">    List<span class="nt">&lt;Entry&gt;</span> out = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">    if (body == null || body.isBlank()) return out;</span></span><span class="line"><span class="cl">    String[] lines = body.split("\\R");</span></span><span class="line"><span class="cl">    for (String line : lines) {</span></span><span class="line"><span class="cl">      if (line.isBlank()) continue;</span></span><span class="line"><span class="cl">      String[] parts = line.split("\\|", 3);</span></span><span class="line"><span class="cl">      if (parts.length<span class="nt">&lt; 3</span><span class="err">)</span><span class="err">continue;</span></span></span><span class="line"><span class="cl">     <span class="err">long</span><span class="na">seq =</span><span class="s">Long.parseLong(parts[0]);</span></span></span><span class="line"><span class="cl">     <span class="err">Instant</span><span class="na">ts =</span><span class="s">Instant.parse(parts[1]);</span></span></span><span class="line"><span class="cl">     <span class="err">String</span><span class="na">text =</span><span class="s">parts[2];</span></span></span><span class="line"><span class="cl">     <span class="err">out.add(new</span><span class="err">Entry(seq,</span><span class="err">ts,</span><span class="err">text));</span></span></span><span class="line"><span class="cl">   <span class="err">}</span></span></span><span class="line"><span class="cl">   <span class="err">return</span><span class="err">out;</span></span></span><span class="line"><span class="cl"> <span class="err">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> <span class="err">public</span><span class="err">List&lt;Entry</span><span class="nt">&gt;</span> fetchSince(long since) {</span></span><span class="line"><span class="cl">    String url = baseUrl + "?since=" + since;</span></span><span class="line"><span class="cl">    return fetch(url);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public List<span class="nt">&lt;Entry&gt;</span> fetchLastN(int n) {</span></span><span class="line"><span class="cl">    String url = baseUrl + "?lastN=" + n;</span></span><span class="line"><span class="cl">    return fetch(url);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private List<span class="nt">&lt;Entry&gt;</span> fetch(String url) {</span></span><span class="line"><span class="cl">    try {</span></span><span class="line"><span class="cl">      HttpRequest req = HttpRequest.newBuilder(URI.create(url))</span></span><span class="line"><span class="cl">          .timeout(Duration.ofSeconds(5)). GET().build();</span></span><span class="line"><span class="cl">      HttpResponse<span class="nt">&lt;String&gt;</span> resp = http.send(req, HttpResponse.BodyHandlers.ofString());</span></span><span class="line"><span class="cl">      if (resp.statusCode() != 200) return List.of();</span></span><span class="line"><span class="cl">      return parse(resp.body());</span></span><span class="line"><span class="cl">    } catch (Exception e) {</span></span><span class="line"><span class="cl">      return List.of();</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h3 id="83-entry">8.3 Entry</h3><p><code>Entry</code>represents a single record with a monotonic sequence number, timestamp, and message content. The immutable record is well suited for display in the grid and for concurrency in the view. In the Vaadin demonstration,<code>Entry serves as a lightweight transport and display object that is generated directly from the REST</code>response and passed on unchanged.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.svenruppert.flow.views.dashboard</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">record</span><span class="nc">Entry</span><span class="p">(</span><span class="kt">long</span><span class="w"/><span class="n">seq</span><span class="p">,</span><span class="w"/><span class="n">Instant</span><span class="w"/><span class="n">ts</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">text</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h3 id="84-sseclientservice">8.4 SseClientService</h3><p>The<code>SseClientService</code>represents the server-side SSEClient of the Vaadin‑application. It maintains a long-lived HTTP‑connection to<code>/sse</code>, parses incoming events in the format<em>text/event-stream</em> and distributes them to registered listeners in the application. After disconnections, a reconnect is automatically attempted. The service‑boundary is deliberately simple: Events are passed on as<code>type</code>/<code>data</code>(especially<code>event: update</code>,<code>data: &lt;seq&gt;</code>). The View then decides whether and how to reload. This preserves the separation between signaling (SSE) and data transmission (REST).</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.flow.views.dashboard;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//--- SSE client (server-side) ---</span></span><span class="line"><span class="cl">public final class SseClientService {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private final HttpClient http = HttpClient.newBuilder()</span></span><span class="line"><span class="cl">      .connectTimeout(Duration.ofSeconds(5)).build();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private final String url;</span></span><span class="line"><span class="cl">  private final List<span class="nt">&lt;Listener&gt;</span> listeners = new CopyOnWriteArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">  private volatile boolean running = false;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public SseClientService(String url) {</span></span><span class="line"><span class="cl">    this.url = Objects.requireNonNull(url);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private static void sleep(long ms) {</span></span><span class="line"><span class="cl">    try {</span></span><span class="line"><span class="cl">      Thread.sleep(ms);</span></span><span class="line"><span class="cl">    } catch (InterruptedException ignored) {</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  public void addListener(Listener l) {</span></span><span class="line"><span class="cl">    listeners.add(l);</span></span><span class="line"><span class="cl">    ensureLoop();</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  public void removeListener(Listener l) {</span></span><span class="line"><span class="cl">    listeners.remove(l);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  private synchronized void ensureLoop() {</span></span><span class="line"><span class="cl">    if (running) return;</span></span><span class="line"><span class="cl">    running = true;</span></span><span class="line"><span class="cl">    Thread t = new Thread(this::loop, "sse-client-loop");</span></span><span class="line"><span class="cl">    t.setDaemon(true);</span></span><span class="line"><span class="cl">    t.start();</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private void loop() {</span></span><span class="line"><span class="cl">    while (running) {</span></span><span class="line"><span class="cl">      try {</span></span><span class="line"><span class="cl">        HttpRequest req = HttpRequest.newBuilder(URI.create(url))</span></span><span class="line"><span class="cl">            .header("Accept", "text/event-stream")</span></span><span class="line"><span class="cl">            .timeout(Duration.ofSeconds(30)). GET().build();</span></span><span class="line"><span class="cl">        HttpResponse<span class="nt">&lt;InputStream&gt;</span> resp = http.send(req, HttpResponse.BodyHandlers.ofInputStream());</span></span><span class="line"><span class="cl">        if (resp.statusCode() != 200) {</span></span><span class="line"><span class="cl">          sleep(1500);</span></span><span class="line"><span class="cl">          continue;</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">        try (var is = resp.body();</span></span><span class="line"><span class="cl">             var br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {</span></span><span class="line"><span class="cl">          String line;</span></span><span class="line"><span class="cl">          String event = "message";</span></span><span class="line"><span class="cl">          StringBuilder data = new StringBuilder();</span></span><span class="line"><span class="cl">          while ((line = br.readLine()) != null) {</span></span><span class="line"><span class="cl">            if (line.isEmpty()) { // Complete event</span></span><span class="line"><span class="cl">              if (!data.isEmpty()) {</span></span><span class="line"><span class="cl">                fire(event, data.toString());</span></span><span class="line"><span class="cl">                data.setLength(0);</span></span><span class="line"><span class="cl">                event = "message";</span></span><span class="line"><span class="cl">              }</span></span><span class="line"><span class="cl">              continue;</span></span><span class="line"><span class="cl">            }</span></span><span class="line"><span class="cl">            if (line.startsWith(":")) continue; Comment</span></span><span class="line"><span class="cl">            if (line.startsWith("event:")) {</span></span><span class="line"><span class="cl">              event = line.substring("event:".length()).trim();</span></span><span class="line"><span class="cl">            } else if (line.startsWith("data:")) {</span></span><span class="line"><span class="cl">              if (!data.isEmpty()) data.append('\n');</span></span><span class="line"><span class="cl">              data.append(line.substring("data:".length()).trim());</span></span><span class="line"><span class="cl">            }</span></span><span class="line"><span class="cl">          }</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">      } catch (Exception e) {</span></span><span class="line"><span class="cl">        sleep(1500);</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  private void fire(String type, String data) {</span></span><span class="line"><span class="cl">    for (listener l : listeners) {</span></span><span class="line"><span class="cl">      try {</span></span><span class="line"><span class="cl">        l.onEvent(type, data);</span></span><span class="line"><span class="cl">      } catch (Exception ignored) {</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public interface Listener {</span></span><span class="line"><span class="cl">    void onEvent(String type, String data);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h3 id="85-uibroadcaster">8.5 UiBroadcaster</h3><p>The<code>UiBroadcaster</code>is a small helper class that distributes UI updates thread-safely to several active UIs. It maintains a list of currently connected<code>UI instances and executes provided commands within the respective UI</code>context (<code>ui.access(...)</code>). This allows reactions to external signals (SSE) to be safely integrated into the VaadinUI without risking UIThread‑violations. The view registers on the attach and deregisters on the detach, thus avoiding zombie‑references.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.flow.views.dashboard;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//--- Simple broadcaster to distribute UI updates thread-safe ---</span></span><span class="line"><span class="cl">public final class UiBroadcaster {</span></span><span class="line"><span class="cl">  private UiBroadcaster() {</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  private static final List<span class="nt">&lt;UI&gt;</span> UI_LIST = new CopyOnWriteArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">  public static void register(UI ui) {</span></span><span class="line"><span class="cl">    UI_LIST.add(ui);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public static void unregister(UI ui) {</span></span><span class="line"><span class="cl">    UI_LIST.remove(ui);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">  public static void broadcast(Command task) {</span></span><span class="line"><span class="cl">    for (UI ui : List.copyOf(UI_LIST)) {</span></span><span class="line"><span class="cl">      try {</span></span><span class="line"><span class="cl">        ui.access(task);</span></span><span class="line"><span class="cl">      } catch (Exception ignored) {</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h2 id="9-summary">9. Summary</h2><h3 id="91-evaluation-of-the-signal-per-sse-data-per-rest-pattern">9.1 Evaluation of the &ldquo;Signal-per-SSE, Data-per-REST&rdquo; pattern</h3><p>The demonstration showed that the separation of signalling and data transmission enables a clear and comprehensible architecture. SSE is ideal for informing clients of changes in real time, while REST takes care of the reliable and flexible delivery of the actual data. This division of labour results in low communication costs and, at the same time, a high level of transparency for users.</p><h3 id="92-didactic-benefits-and-reusability">9.2 Didactic benefits and reusability</h3><p>The scenario presented is deliberately kept simple to make the functionality of SSE in interaction with a Vaadin Flow application understandable. The integration of a CLI for data entry makes it easier to understand the chain of events, signals and retrieval clearly. This makes the example suitable not only for technical experiments, but also for training, workshops and teaching materials. Due to its modular structure, it can be easily extended or integrated into more complex projects, for example, as a basis for further experiments in the context of the URL shortener open source project.</p>
]]></content:encoded><category>Uncategorized</category><media:content url="https://svenruppert.com/images/2025/09/ChatGPT-Image-2.-Sept.-2025-19_29_25.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/09/ChatGPT-Image-2.-Sept.-2025-19_29_25.jpeg"/><enclosure url="https://svenruppert.com/images/2025/09/ChatGPT-Image-2.-Sept.-2025-19_29_25.jpeg" type="image/jpeg" length="0"/></item><item><title>How and why to use the classic Observer pattern in Vaadin Flow</title><link>https://svenruppert.com/posts/how-and-why-to-use-the-classic-observer-pattern-in-vaadin-flow/</link><pubDate>Mon, 01 Sep 2025 11:32:18 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/how-and-why-to-use-the-classic-observer-pattern-in-vaadin-flow/</guid><description>1. Introduction and motivation The observer pattern is one of the basic design patterns of software development and is traditionally used to decouple state changes and process them. Its origins lie in the development of graphical user interfaces, where a shift in the data model required synchronising several views immediately, without a direct link between these views. This pattern quickly established itself as the standard solution to promote loosely coupled architectures.</description><content:encoded>&lt;![CDATA[<h2 id="1-introduction-and-motivation">1. Introduction and motivation</h2><p>The observer pattern is one of the basic design patterns of software development and is traditionally used to decouple state changes and process them. Its origins lie in the development of graphical user interfaces, where a shift in the data model required synchronising several views immediately, without a direct link between these views. This pattern quickly established itself as the standard solution to promote loosely coupled architectures.</p><ol><li><a href="https://svenruppert.com/2025/09/01/how-and-why-to-use-the-classic-observer-pattern-in-vaadin-flow/#1-introduction-and-motivation">1. Introduction and motivation</a></li><li><a href="https://svenruppert.com/2025/09/01/how-and-why-to-use-the-classic-observer-pattern-in-vaadin-flow/#2-the-classic-observer-pattern">2. The classic observer pattern</a></li><li><a href="https://svenruppert.com/2025/09/01/how-and-why-to-use-the-classic-observer-pattern-in-vaadin-flow/#3-observer-pattern-in-vaadin-flow">3. Observer Pattern in Vaadin Flow</a></li><li><a href="https://svenruppert.com/2025/09/01/how-and-why-to-use-the-classic-observer-pattern-in-vaadin-flow/#4-differences-between-classic-observer-and-vaadin-flow">4. Differences between classic Observer and Vaadin Flow</a></li><li><a href="https://svenruppert.com/2025/09/01/how-and-why-to-use-the-classic-observer-pattern-in-vaadin-flow/#5-when-does-the-classic-observer-pattern-still-make-sense">5. When does the classic observer pattern still make sense?</a></li><li><a href="https://svenruppert.com/2025/09/01/how-and-why-to-use-the-classic-observer-pattern-in-vaadin-flow/#6-example-combination-of-observer-pattern-and-vaadin-flow-with-external-producer">6. Example: Combination of Observer Pattern and Vaadin Flow (with external producer)</a></li><li><a href="https://svenruppert.com/2025/09/01/how-and-why-to-use-the-classic-observer-pattern-in-vaadin-flow/#7-best-practices-and-pitfalls">7. Best Practices and Pitfalls</a></li><li><a href="https://svenruppert.com/2025/09/01/how-and-why-to-use-the-classic-observer-pattern-in-vaadin-flow/#8-conclusion">8. Conclusion</a></li></ol><p>At its core, the Observer Pattern addresses the challenge that events or state changes do not remain isolated; instead, they must be processed by different components. This creates an asynchronous notification model: The subject informs all registered observers as soon as its state changes. The observers react to this individually, without the subject having to know their concrete implementations.</p><div class="pull-quote"><p><strong><em>The source code with the examples can be found in the following git-repo:</em></strong><a href="https://github.com/Java-Publications/Blog---Vaadin---How-to-use-the-Observer-Pattern-and-why"><em>https://github.com/Java-Publications/Blog&mdash;Vaadin&mdash;How-to-use-the-Observer-Pattern-and-why</em></a>** __**</p></div><p>In modern software architectures, characterised by interactivity, parallelism, and distributed systems, this basic principle remains highly relevant. It continues to be used in the field of UI development, as user interactions and data flows must be handled dynamically and in a reactive manner. Frameworks such as Vaadin Flow take up this idea, but go beyond the classic implementation by providing mechanisms for state matching, server-side synchronisation and client-side updates.</p><p>The motivation to apply the Observer Pattern in the context of Vaadin Flow lies in comparing proven yet simple patterns and their further development within a modern UI framework. This not only sharpens the understanding of loose coupling and event handling, but also creates a practical reference to today&rsquo;s web applications.</p><h2 id="2-the-classic-observer-pattern">2. The classic observer pattern</h2><p>The classical observer pattern describes the relationship between a subject and a set of observers. The subject maintains the current state and automatically informs all registered observers of any relevant change. This achieves a loose coupling: the subject only knows the interface of its observers, but not their concrete implementation or functionality.</p><p>In its original form, according to the &ldquo;Gang of Four&rdquo; definition, the pattern consists of two central roles. The subject manages the list of its observers and provides methods of logging in and out. As soon as the internal state changes, it calls a notification method for all observers. The observers implement an interface through which they are informed and can define their own reaction. This allows state changes to be propagated without creating complex coupling or cyclic dependencies.</p><p>To avoid outdated dependencies and to maintain control over the API, an independent, generic variant with its own interfaces is shown below. The aim is to demonstrate how it works in a JUnit 5 test without the main method. The output is a logger via<strong>HasLogger</strong> from the package<strong>com.svenruppert.dependencies.core.logger</strong>.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">package com.svenruppert.demo;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">import com.svenruppert.dependencies.core.logger.HasLogger;</span></span><span class="line"><span class="cl">import org.junit.jupiter.api.Test;</span></span><span class="line"><span class="cl">import java.util.List;</span></span><span class="line"><span class="cl">import java.util.concurrent.CopyOnWriteArrayList;</span></span><span class="line"><span class="cl">import static org.junit.jupiter.api.Assertions.assertEquals;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public class ObserverDemoTest {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Test</span></span><span class="line"><span class="cl"> void beobachter_erhaelt_updates_und_letzten_wert() {</span></span><span class="line"><span class="cl"> var sensor = new TemperatureSensor();</span></span><span class="line"><span class="cl"> var anzeigeA = new TemperaturAnzeige();</span></span><span class="line"><span class="cl"> var displayB = new Temperature display();</span></span><span class="line"><span class="cl"> sensor.addObserver(displayA);</span></span><span class="line"><span class="cl"> sensor.addObserver(displayB);</span></span><span class="line"><span class="cl"> sensor.setTemperature(22);</span></span><span class="line"><span class="cl"> assertEquals(22, displayA.lastObservedTemp);</span></span><span class="line"><span class="cl"> assertEquals(22, sensor.getTemperature());</span></span><span class="line"><span class="cl"> sensor.setTemperature(25);</span></span><span class="line"><span class="cl"> assertEquals(25, sensor.getTemperature());</span></span><span class="line"><span class="cl"> assertEquals(25, displayA.lastObservedTemp);</span></span><span class="line"><span class="cl"> assertEquals(25, displayB.lastObservedTemp);</span></span><span class="line"><span class="cl"> sensor.removeObserver(displayA);</span></span><span class="line"><span class="cl"> sensor.setTemperature(21);</span></span><span class="line"><span class="cl"> assertEquals(21, sensor.getTemperature());</span></span><span class="line"><span class="cl"> assertEquals(25, displayA.lastObservedTemp);</span></span><span class="line"><span class="cl"> assertEquals(21, displayB.lastObservedTemp);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Minimalist, own Observer API (generic, thread-safe implemented in the subject).</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> interface Observer<span class="nt">&lt;T&gt;</span> {</span></span><span class="line"><span class="cl"> void onUpdate(T value);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> interface Subject<span class="nt">&lt;T&gt;</span> {</span></span><span class="line"><span class="cl"> void addObserver(Observer<span class="nt">&lt;T&gt;</span> o);</span></span><span class="line"><span class="cl"> void removeObserver(Observer<span class="nt">&lt;T&gt;</span> o);</span></span><span class="line"><span class="cl"> void notifyObservers(T value);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Concrete subject: maintains a temperature and informs observers of changes.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> static class Temperature Sensor</span></span><span class="line"><span class="cl"> implements Subject<span class="nt">&lt;Integer&gt;</span> {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private final List<span class="nt">&lt;Observer</span><span class="err">&lt;Integer</span><span class="nt">&gt;</span>&gt; observers = new CopyOnWriteArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Private int temperature;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void addObserver(Observer<span class="nt">&lt;Integer&gt;</span> o) { observers.add(o); }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void removeObserver(Observer<span class="nt">&lt;Integer&gt;</span> o) { observers.remove(o); }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void notifyObservers(Integer value) { observers.forEach(o -&gt; o.onUpdate(value)); }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public int getTemperatur() { return temperatur; }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public void setTemperature(int newtemperature) {</span></span><span class="line"><span class="cl"> this.temperature = newtemperature;</span></span><span class="line"><span class="cl"> notifyObservers(temperature);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Concrete Observer: remembers the last value and logs it.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> static class temperature display</span></span><span class="line"><span class="cl"> implements Observer<span class="nt">&lt;Integer&gt;</span>, HasLogger {</span></span><span class="line"><span class="cl"> Integer lastObservedTemp;</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void onUpdate(Integer value) {</span></span><span class="line"><span class="cl"> lastObservedTemp = value;</span></span><span class="line"><span class="cl"> logger().info("New temperature: {}°C", value);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>In the example, the company&rsquo;s own API defines two lean interfaces: Subject<T> manages observers and propagates changes, while Observer<T> reacts to updates. The implementation of the Subject uses a CopyOnWriteArrayList to handle concurrent logins and logoffs during notification robustly. The JUnit5Test demonstrates the expected behaviour: By setting the temperature twice, notifications are triggered twice. The temperature display logs the values via HasLogger and retains the last observed value for assertion. This demonstrates the basic semantics of the Observer Pattern – loose coupling, clear responsibilities and event-driven updating – in a modern, framework-free Java variant.</p><h2 id="3-observer-pattern-in-vaadin-flow">3. Observer Pattern in Vaadin Flow</h2><p>While the classic Observer Pattern has its origins in desktop programming, Vaadin Flow elevates the concept to a higher level of abstraction. Since Vaadin operates on the server side and manages the UI state centrally, synchronisation between the data model, user interface, and user interactions is an integral part of the framework. Events and listeners take on the role of the observer mechanism.</p><p>A central element is the<strong>ValueChangeListener</strong> , which is automatically triggered when changes are made to input components such as text fields or checkboxes. Here, the UI component acts as a subject, while listeners represent the observer. As soon as the user makes an input, an event is generated, and all registered listeners are informed. All the developer has to do is implement the desired response without worrying about chaining event flows.</p><p>Additionally, Vaadin provides a powerful tool with the<strong>Binder</strong> , which enables bidirectional data binding between the data model and UI components. Changes to a field in the form are automatically propagated to the bound object, while changes in the object are immediately visible in the UI elements. The binder thus adopts a bidirectional observation that goes beyond the possibilities of the classic observer pattern.</p><p>Another example of the observer pattern&rsquo;s implementation in Vaadin Flow is the<strong>EventBus mechanism</strong> , which enables components or services to exchange information about events in a loosely coupled manner. Here, it becomes clear that Vaadin adopts the basic idea of the pattern, but expands it in the context of a web application to include aspects such as thread security, lifecycle management, and client-server synchronisation.</p><p>The strength of Vaadin Flow lies in the fact that developers no longer have to implement observer interfaces explicitly; instead, they can establish observation relationships via declarative APIs, such as addValueChangeListener, addClickListener, or via binder bindings. This does not abolish the pattern, but integrates it deeply into the architecture and adapts it to the special requirements of a server-side web UI.</p><p>Vaadin Flow implements the basic idea of the Observer Pattern along two levels. At the UI level, component-specific events (e.g., ValueChangeEvent, ClickEvent) exist that are registered via listeners and trigger changes to the representation or state model. This mechanism corresponds to a finely granulated, component-internal observer and is conveyed via an internal EventBus for each component. At the application level, a domain-level observation structure is also recommended to model state changes outside the UI and to mirror them in the UI in a decoupled manner. This allows long-lived domain objects and short-lived UII punches to be cleanly separated.</p><p>A minimal example exclusively with Vaadin on-board tools shows the observation of UIC conditions as well as the binding to a model via binder:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.svenruppert.flow.MainLayout</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.button.Button</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.html.Div</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.orderedlayout.VerticalLayout</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.textfield.TextField</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.data.binder.binder</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.router.Route</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"observer-bordmittel"</span><span class="p">,</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainLayout</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">On</span><span class="o">-</span><span class="n">board</span><span class="w"/><span class="n">ResourcesView</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">BordmittelView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">(</span><span class="s">"Name"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">log</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Div</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">save</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Save"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//(1) Component-internal observer: reacts to ValueChangeEvent</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">name</span><span class="p">.</span><span class="na">addValueChangeListener</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">log</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">"UI → UI: Name changed to '"</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getValue</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"'"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//(2) Binder as a bidirectional observation between UI and model</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">binder</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Binder</span><span class="o">&lt;&gt;</span><span class="p">(</span><span class="n">Person</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">model</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Person</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">binder</span><span class="p">.</span><span class="na">forField</span><span class="p">(</span><span class="n">name</span><span class="p">).</span><span class="na">bind</span><span class="p">(</span><span class="n">Person</span><span class="p">::</span><span class="n">getName</span><span class="p">,</span><span class="w"/><span class="n">Person</span><span class="p">::</span><span class="n">setName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">binder</span><span class="p">.</span><span class="na">setBean</span><span class="p">(</span><span class="n">model</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// (3) Action: reads current model state (powered by the binder)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">store</span><span class="p">.</span><span class="na">addClickListener</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">log</span><span class="p">.</span><span class="na">setText</span><span class="p">(</span><span class="s">"Model → Action: saved name = "</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="n">model</span><span class="p">.</span><span class="na">getName</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">add</span><span class="p">(</span><span class="n">name</span><span class="p">,</span><span class="w"/><span class="n">store</span><span class="p">,</span><span class="w"/><span class="n">log</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">class</span><span class="nc">Person</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">getName</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setName</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here, addValueChangeListener(&hellip;), the role of the observer at the component level, while the binder synchronises the changes between the input field and the domain object. Without additional infrastructure, this creates a transparent, declarative observation chain from the UI to the model and back to the UI. It is important to note that the button does not trigger a new save, but reads the model state that has already been synchronised by the binder and makes it visible. It thus serves as an action-level observer that confirms and logs the current consistency between UI and model.</p><h2 id="4-differences-between-classic-observer-and-vaadin-flow">4. Differences between classic Observer and Vaadin Flow</h2><p>The comparison between the classic Observer Pattern and its implementation in Vaadin Flow reveals that both approaches share the same basic idea, but operate at different levels of abstraction. While the classic pattern relies on purely object-oriented structures – subject and observer communicate directly via defined interfaces – Vaadin Flow shifts observation to a framework-supported environment with integrated event control.</p><p>A significant difference lies in<strong>life cycle management</strong>. In the classic observer, subjects and observers remain the responsibility of the developer; Registration and deregistration must be done manually, otherwise there is a risk of memory leaks. Vaadin Flow integrates observation into the lifecycle of UI components. Listeners are registered when a component is created and automatically resolved when the component is removed, reducing administrative overhead.</p><p>ThreadSecurity also differs significantly. The classic observer pattern works in a local context and has no UI thread binding. Vaadin Flow, on the other hand, must guarantee that UI updates are done exclusively in the UI thread. This forces the use of mechanisms such as UI.access(&hellip;)as soon as events from foreign threads are processed.</p><p>Another difference concerns the<strong>direction and range of the synchronisation</strong>. In the classic model, notifications are limited to the objects involved. Vaadin Flow extends this principle to client-server communication: changes to the UI are automatically synchronised to the server, while server-side state changes are, in turn, made visible in the UI on the client side.</p><p>Finally, the<strong>granularity</strong> of the observation varies. Classic observers usually react to gross changes in state. Vaadin Flow, on the other hand, offers a variety of specialised event types (e.g. ValueChange, Click, Attach/Detach) that allow for very finely tuned reactions. In combination with the binder , a bidirectional coupling between model and UI is created that goes far beyond the original idea of the pattern.</p><p>In summary, the classic observer pattern provides the theoretical basis. Still, Vaadin Flow abstracts and expands it by integrating lifecycle, thread security, and client-server synchronisation, thereby enabling a higher degree of robustness and productivity.</p><h2 id="5-when-does-the-classic-observer-pattern-still-make-sense">5. When does the classic observer pattern still make sense?</h2><p>Although Vaadin Flow already integrates many observation mechanisms, there are scenarios in which the classic observer pattern can also be used sensibly in modern Vaadin applications. The crucial point here is the<strong>separation between UI logic and domain logic</strong>. While listeners and binders in Vaadin handle the synchronisation between user input and UI components, events often arise in the business layer that must be managed independently of the UI.</p><p>A typical field of application is<strong>internal domain events</strong>. For example, a background process can receive a new message, complete a calculation, or import data from an external system. These events should not be directly bound to UI components; instead, they should be distributed throughout the system. Here, the classic observer pattern is suitable for informing interested services or components of such changes without creating a direct link.</p><p>The pattern also plays a role in the context of<strong>integrations with external systems</strong>. When a REST service or messaging infrastructure feeds events into the system, the observer pattern can be used to propagate those events within the domain first. Only in the second step is it decided whether and how this information is passed on to a Vaadin UI. This keeps the dependency on the UI low, and the domain layer remains usable outside of a UI context.</p><p>Another reason for using the classic observer pattern is testability<strong>and reusability</strong>. Unit testing at the domain layer is easier to perform when observation mechanisms do not depend on Vaadin-specific APIs. This allows complex interactions to be tested in isolation before they are later wired to the UI.</p><p>In summary, the classic observer pattern complements the mechanisms available in Vaadin Flow for UI-independent, domain-internal events or integrations with external sources. It enables loose coupling, increases testability and ensures clear demarcations between layers – a principle that is a decisive advantage, especially in more complex applications.</p><h2 id="6-example-combination-of-observer-pattern-and-vaadin-flow-with-external-producer">6. Example: Combination of Observer Pattern and Vaadin Flow (with external producer)</h2><p>An efficient variant arises when messages are not generated by the UI itself, but come from an external producer outside the UI life cycle. Typical sources are background processes, REST integrations or message queues. The Observer Pattern ensures that these events can be distributed to all interested components, regardless of the UI.</p><p>The following example extends the previous construction: A MessageProducer periodically generates messages in a separate thread and passes them to the MessageService. The Vaadin view remains a pure observer, reflecting only the messages in the UI.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">//Bootstrap: starts the producer on startup</span></span><span class="line"><span class="cl">final class ApplicationBootstrap {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private static final MessageService SERVICE = new MessageService();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> static {</span></span><span class="line"><span class="cl"> MessageProducer.start(SERVICE);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> private ApplicationBootstrap() {</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> static MessageService messageService() {</span></span><span class="line"><span class="cl"> return SERVICE;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//External producer that periodically generates news</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public class MessageProducer {</span></span><span class="line"><span class="cl"> private static ScheduledExecutorService scheduler;</span></span><span class="line"><span class="cl"> private MessageProducer() { }</span></span><span class="line"><span class="cl"> static void start(MessageService service) {</span></span><span class="line"><span class="cl"> scheduler = Executors.newSingleThreadScheduledExecutor();</span></span><span class="line"><span class="cl"> scheduler.scheduleAtFixedRate(</span></span><span class="line"><span class="cl"> new CronJob(service),</span></span><span class="line"><span class="cl"> 0, 2, SECONDS);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> static void stop() {</span></span><span class="line"><span class="cl"> if (scheduler != null) {</span></span><span class="line"><span class="cl"> scheduler.shutdownNow();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public record CronJob(MessageService service)</span></span><span class="line"><span class="cl"> implements Runnable, HasLogger {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void run() {</span></span><span class="line"><span class="cl"> logger().info("Next message will be sent..");</span></span><span class="line"><span class="cl"> service.newMessage("Tick @ " + Instant.now());</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">//Domain Service</span></span><span class="line"><span class="cl">public class MessageService</span></span><span class="line"><span class="cl"> implements Subject<span class="nt">&lt;String&gt;</span> , HasLogger {</span></span><span class="line"><span class="cl"> private final List<span class="nt">&lt;Observer</span><span class="err">&lt;String</span><span class="nt">&gt;</span>&gt; observers = new CopyOnWriteArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void addObserver(Observer<span class="nt">&lt;String&gt;</span> o) {</span></span><span class="line"><span class="cl"> logger().info("New Observer added: {}", o);</span></span><span class="line"><span class="cl"> observers.add(o);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void removeObserver(Observer<span class="nt">&lt;String&gt;</span> o) {</span></span><span class="line"><span class="cl"> logger().info("Observer removed: {}", o);</span></span><span class="line"><span class="cl"> observers.remove(o);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void notifyObservers(String value) {</span></span><span class="line"><span class="cl"> logger().info("Notifying {} observers with the value {} ", observers.size(), value);</span></span><span class="line"><span class="cl"> observers.forEach(o -&gt; o.onUpdate(value));</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public void newMessage(String msg) {</span></span><span class="line"><span class="cl"> notifyObservers(msg);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public interface Observer<span class="nt">&lt;T&gt;</span> {</span></span><span class="line"><span class="cl"> void onUpdate(T value);</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public interface Subject<span class="nt">&lt;T&gt;</span> {</span></span><span class="line"><span class="cl"> void addObserver(Observer<span class="nt">&lt;T&gt;</span> o);</span></span><span class="line"><span class="cl"> void removeObserver(Observer<span class="nt">&lt;T&gt;</span> o);</span></span><span class="line"><span class="cl"> void notifyObservers(T value);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This structure creates a complete message flow<strong>producer → domain → UI</strong>. The domain layer is independent of Vaadin and can be tested in isolation. The UI remains limited to the display and updates itself exclusively in the UI thread.</p><p>The new messages appear every two seconds, as the MessageProducer is configured in the background with scheduleAtFixedRate. When it starts, it immediately generates a message and then periodically generates a new one every two seconds. These are distributed to the registered observers via the MessageService.<strong>Important:</strong> For server-side changes to be reflected in the browser without user interaction, ServerPush or Polling must be enabled. In this example, @Push on the View ensures that ui.access(&hellip;) actively sends the changes to the client. Alternatively, polling (e.g. @Poll(2000) or UI#setPollInterval). The view receives the events and sets the text in the div using UI.access(&hellip;) in UI Thread, so that the most recent message is visible.</p><p>This uses the Observer Pattern to integrate external events into a Vaadin Flow application reliably.</p><h2 id="7-best-practices-and-pitfalls">7. Best Practices and Pitfalls</h2><p>The integration of the classic Observer Pattern into a Vaadin Flow application brings both advantages and specific challenges. To ensure a robust and maintainable architecture, several best practices should be considered.</p><p><strong>Observer lifecycle management.</strong> A common mistake is to leave Observer permanently registered in the service, even if the associated UI component has already been destroyed. This leads to memory leaks and unexpected calls to views that no longer exist. Therefore, observers in Vaadin views should always be registered inonAttach and removed in onDetach. In this way, the observation remains clearly linked to the life cycle of the UI.</p><p><strong>Use of suitable data structures</strong><br>
Instead of simple lists, it is recommended to use concurrent, duplicate-free data structures, such as ConcurrentHashMap or CopyOnWriteArraySet, for managing observers. This avoids duplicate registrations and ensures thread safety, even if multiple threads add or remove observers at the same time.</p><p><strong>Thread security and UI updates.</strong> Since external events are often generated in separate threads, it is imperative to install UI updates in Vaadin viaUI.access(&hellip;) be performed. Otherwise, there is a risk of race conditions and exceptions, because the UI components may only be changed from within the UI thread. Additionally, server push or polling should be enabled to ensure that changes are sent to the client without user interaction.</p><p><strong>Domain and UI demarcation.</strong> The domain layer should remain completely free of Vaadin-specific dependencies. This ensures that business logic can be tested and reused outside of the UI context. The UI only registers itself as an observer and takes over the representation of the events delivered by the domain.</p><p><strong>Error handling and logging</strong><br>
Events should be handled robustly to prevent individual errors from interrupting the entire notification flow. Clean logging (e.g. via a common HasLogger interface) facilitates the analysis and traceability of the event flow.</p><p><strong>Summary.</strong> The main pitfalls lie in improper lifecycle management, a lack of thread security, and overly tight coupling between the UI and domain. When these aspects are taken into account, the combination of the Observer Pattern and Vaadin Flow enables a clear separation of responsibilities, testable business logic, and a reactive, user-friendly interface.</p><h2 id="8-conclusion">8. Conclusion</h2><p>A look at the Observer Pattern in combination with Vaadin Flow reveals that it remains a relevant and compelling design pattern, whose benefits extend beyond classic desktop or console applications. In its original form, it provides apparent decoupling of state changes and their processing, thus creating a clean basis for loosely coupled architectures.</p><p>Vaadin Flow abstracts and extends this approach by integrating observation mechanisms directly into the lifecycle of UI components, providing a variety of specialised events, and automating synchronisation between server and client. This relieves the developer of many tasks that could still be solved manually in the classic observer pattern, such as memory management or thread security.</p><p>Nevertheless, the classic observer pattern remains essential in areas where UI-independent events occur or external systems are connected. The combination of both approaches – a clearly defined domain layer with observers and a Vaadin UI based on it – creates an architecture that is both loosely coupled, testable, and extensible.</p><p>Overall, it can be said that the Observer Pattern forms the theoretical basis, while Vaadin Flow provides the practical implementation for modern web applications. Those who consciously combine both approaches benefit from robust and flexible systems that are prepared for future requirements.</p>
]]></content:encoded><category>Design Pattern</category><category>Java</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/08/ChatGPT-Image-29.-Aug.-2025-14_33_17.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/08/ChatGPT-Image-29.-Aug.-2025-14_33_17.jpeg"/><enclosure url="https://svenruppert.com/images/2025/08/ChatGPT-Image-29.-Aug.-2025-14_33_17.jpeg" type="image/jpeg" length="0"/></item><item><title>Password Security: Why Hashing is Essential</title><link>https://svenruppert.com/posts/password-security-why-hashing-is-essential/</link><pubDate>Fri, 29 Aug 2025 14:53:27 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/password-security-why-hashing-is-essential/</guid><description>Password security is an often underestimated but critical topic in software development. Databases containing millions of user logins are repeatedly compromised - and shockingly, often, it turns out that passwords have been stored in plain text. This gives attackers direct access to sensitive account data and opens the door to identity theft, account takeovers and other attacks.</description><content:encoded>&lt;![CDATA[<p>Password security is an often underestimated but critical topic in software development. Databases containing millions of user logins are repeatedly compromised - and shockingly, often, it turns out that passwords have been stored in plain text. This gives attackers direct access to sensitive account data and opens the door to identity theft, account takeovers and other attacks.</p><p>In this blog post, we discuss why passwords should always be stored hashed, the attack methods available, and how you can implement an initial secure implementation with Java in your application. We also examine the differences between PBKDF2, BCrypt, and Argon2 and explain best practices for handling passwords in software development.</p><ol><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#passwords-and-hashing">Passwords and hashing</a></li><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#what-are-brute-force-and-rainbow-table-attacks-on-passwords">What are brute force and rainbow table attacks on passwords?</a></li><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#and-how-do-i-do-this-now-in-my-application">And how do I do this now in my application?</a></li><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#how-safe-is-pbkdf2">How safe is PBKDF2?</a></li><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#using-stronger-hashing-algorithms-like-argon2">Using stronger hashing algorithms like Argon2</a></li><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#application-of-argon2-in-java">Application of Argon2 in Java</a></li><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#generate-the-salt-value">Generate the SALT value.</a></li><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#what-is-the-procedure-for-checking-the-username-password-combination-during-the-login-process">What is the procedure for checking the username-password combination during the login process?</a></li><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#a-few-more-words-about-strings-in-java">A few more words about strings in Java</a><ol><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#so-what-can-you-do-about-it-now">So what can you do about it now?</a></li></ol></li><li><a href="https://svenruppert.com/2025/01/29/password-security-why-hashing-is-essential/#conclusion">Conclusion</a></li></ol><h2 id="passwords-and-hashing">Passwords and hashing</h2><p>Passwords should never be stored in plain text but should always be hashed to ensure the security of user data and prevent misuse. If a database is compromised and passwords are only stored in plain text, attackers have direct access to users&rsquo; sensitive credentials. This can have serious consequences, as many people use the same password for multiple services. Hashing passwords significantly reduces this risk because attackers only see the hash values ​​, not the actual ones.</p><p>A key advantage of hashing is its one-way function. A hash value can be generated from a password, but inferring the original password is virtually impossible. This makes it extremely difficult to misuse the access data resulting from a data leak. This protection mechanism applies not only to external attacks but also to internal security risks. If passwords are stored in plain text, employees with access to the database, for example, could view and misuse this information. Hashed passwords largely eliminate such insider threats.</p><p>In addition, compliance with legal requirements and security standards such as the General Data Protection Regulation (GDPR) or the Payment Card Industry Data Security Standard (PCI-DSS) requires the protection of passwords. Hashing passwords is important to meet these standards and avoid legal consequences.</p><h2 id="what-are-brute-force-and-rainbow-table-attacks-on-passwords">What are brute force and rainbow table attacks on passwords?</h2><p>Brute force attacks and rainbow table attacks are two methods attackers use to decrypt passwords or other secrets. A<strong>Brute force attack</strong> works by systematically trying every possible password combination until finding the right one. We start with the simplest combinations, such as “aaa” or “1234”, and check every possible variant. Although brute force attacks can theoretically always be successful, their effectiveness depends heavily on the complexity of the password and computing power. A short password can be cracked in a few seconds, while a long and complex password increases the effort significantly. Measures such as longer passwords, limiting the number of login attempts per unit of time (e.g. blocking after several failed attempts) and the use of hashing algorithms with many iterations make brute force attacks significantly more difficult and time-consuming.</p><p>In contrast, the<strong>Rainbow table attacks</strong> pre-built tables that contain many hash values ​​and their associated plaintext passwords. Attackers compare the stored hash of a password with the hashes in the table to find the original password. This method is significantly faster than brute force because the passwords must not be re-hashed every time. However, rainbow tables only work if the hashing method does not use a salt value. A salt value is a random value added to each password hash, so even identical passwords produce different hashes. Without salt, attackers could use the same table across many other systems, but this is no longer possible with salt.</p><p>Using modern hashing algorithms such as PBKDF2, BCrypt, or Argon2 is crucial to protecting yourself from both attack methods. These algorithms combine salts and multiple iterations to significantly increase attacker effort. Additionally, long and complex passwords make brute-force attacks virtually impossible as the number of possible combinations increases exponentially. In summary, combining strong passwords, salts, and secure hashing algorithms effectively defends against brute force and rainbow table attacks.</p><h2 id="and-how-do-i-do-this-now-in-my-application">And how do I do this now in my application?</h2><p>To securely hash a password in Java, it is recommended to use a specialised hash function like<strong>PBKDF2</strong> ,<strong>BCrypt</strong> , or Argon2. These algorithms are specifically designed to make attacks such as brute force or rainbow table attacks more difficult. One way to implement this with the Java standard library is to use<strong>PBKDF2</strong>.</p><p>First, a<strong>Salt</strong> is generated, a random sequence of bytes generated for each password individually to ensure that two identical passwords have different hashes. The class SecureRandom can be used to create a 16-byte salt. Then, with the class PBEKeySpec, a key is generated based on the password, the salt, the desired number of iterations (e.g. 65,536) and the key length (e.g. 256 bits). With the help of SecretKeyFactory and the algorithm specification &ldquo;PBKDF2WithHmacSHA256&rdquo; the password hash is created. The resulting hash is finally encoded in Base64 to store it in a readable form.</p><p>The hashed password is often stored along with the salt, separated by a colon (:). This makes it easier to verify later by extracting the salt again and using it for the hash calculation. In addition, there are also external libraries such as<strong>Spring Security</strong> or<strong>BouncyCastle</strong> , which allow easier integration of algorithms like<strong>BCrypt</strong> or<strong>Argon2</strong> make possible. These often offer even more security and flexibility.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.NoSuchAlgorithmException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.spec.InvalidKeySpecException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.spec.KeySpec</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.Base64</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javax.crypto.SecretKeyFactory</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javax.crypto.spec.PBEKeySpec</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">PasswordHasher</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">ITERATIONS</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">65536</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">KEY_LENGTH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">256</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">ALGORITHM</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"PBKDF2WithHmacSHA256"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">hashPassword</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">salt</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">KeySpec</span><span class="w"/><span class="n">spec</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">PBEKeySpec</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">password</span><span class="p">.</span><span class="na">toCharArray</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">salt</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ITERATIONS</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">KEY_LENGTH</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SecretKeyFactory</span><span class="w"/><span class="n">factory</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">SecretKeyFactory</span><span class="p">.</span><span class="na">getInstance</span><span class="p">(</span><span class="n">ALGORITHM</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">hash</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">factory</span><span class="p">.</span><span class="na">generateSecret</span><span class="p">(</span><span class="n">spec</span><span class="p">).</span><span class="na">getEncoded</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Base64</span><span class="p">.</span><span class="na">getEncoder</span><span class="p">().</span><span class="na">encodeToString</span><span class="p">(</span><span class="n">hash</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">NoSuchAlgorithmException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">InvalidKeySpecException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RuntimeException</span><span class="p">(</span><span class="s">"Error while hashing password"</span><span class="p">,</span><span class="w"/><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"myPassword"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">salt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"randomSalt"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">hashedPassword</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">hashPassword</span><span class="p">(</span><span class="n">password</span><span class="p">,</span><span class="w"/><span class="n">salt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"hasehd password: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">hashedPassword</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>However, there are a few points here that you should take a closer look at.</p><h2 id="how-safe-is-pbkdf2">How safe is PBKDF2?</h2><p>PBKDF2 is a proven and secure password hashing algorithm, but its security depends heavily on the correct implementation and parameters used. Various factors influence the safety of PBKDF2. A key aspect is the number of iterations, representing the work factor. The higher the number of iterations, the more computing power is required for hashing. Experts recommend at least 100,000 iterations, although even higher values ​​are often chosen for modern applications to make brute-force attacks more difficult. Another crucial factor is the salt value, a random and unique value that is regenerated for each password. In addition, PBKDF2 can be configured with a variable key length, for example, 256 bits, which increases security. Modern applications often use hash functions such as SHA-256 or SHA-512.</p><p>PBKDF2 has several strengths. The algorithm is proven and has been used for years in security-critical applications such as WPA2 and encrypted storage systems. It is easy to implement as extensive support in many programming languages ​​and libraries is based on a recognised standard defined in RFC 8018. Nevertheless, PBKDF2 also has weaknesses. The algorithm is not optimised explicitly against GPU or ASIC-based attacks, allowing attackers with specialised hardware to efficiently carry out brute force attacks. Compared to modern algorithms such as Argon2, PBKDF2 requires a higher number of iterations to achieve similar levels of security. In addition, the further development of PBKDF2 is progressing more slowly, which means it is considered less adapted to current threats than modern alternatives such as Argon2.</p><p>However, PBKDF2 can be used safely if a few key conditions are met: a unique salt value, the iteration count should be at least 100,000 (ideally more), and a modern hash function such as SHA-256 should be used. In addition, strong password guidelines, such as a sufficient minimum length, should complement security.</p><p>Argon2 is increasingly recommended as an alternative to PBKDF2. Argon2, the Password Hashing Competition (PHC) winner in 2015, is more modern and better adapted to current threats. Its memory intensity provides better protection against GPU and ASIC attacks and offers flexible configuration options regarding work factor, memory requirements and parallelism.</p><p>In summary, PBKDF2 is secure when implemented correctly but has weaknesses compared to specialised hardware. Argon2 is preferable for new applications because it is better suited to modern security requirements.</p><h2 id="using-stronger-hashing-algorithms-like-argon2">Using stronger hashing algorithms like Argon2</h2><p>Argon2 is a modern algorithm for secure password hashing and was developed in 2015 as part of the<strong>Password Hashing Competition (PHC)</strong>. It is considered one of the most secure approaches to storing passwords today and, as already mentioned, offers adequate protection against brute force attacks as well as attacks using GPUs or specialised hardware solutions such as ASICs. This is achieved due to the fact that Argon2 is both memory and compute-intensive, forcing attackers with parallel hardware to expend significant resources.</p><p>Argon2 exists in three variants, each optimised for different use cases. The first variant,<strong>Argon2i</strong> , is designed to be particularly memory-safe. It performs memory-intensive operations independently of the input data and is resistant to side-channel attacks such as timing attacks. This makes Argon2i ideal for applications where data protection is a top priority.</p><p>The second variant,<strong>Argon2d</strong> , is specifically optimised for attack resistance. It works data-dependently, which makes it particularly robust against GPU-based attacks. However, Argon2d is more vulnerable to side-channel attacks and is, therefore, less suitable for securing sensitive data.</p><p>The third variant, Argon2id, offers a balanced combination of both approaches. It starts with a memory-safe approach, like that used by Argon2i, and then moves to a data-dependent process that ensures attack resistance. This mix makes Argon2id the preferred choice for most use cases, as it combines data protection and attack resilience.</p><p>One of Argon2&rsquo;s key strengths is its customizability. The algorithm allows developers to configure three primary parameters: memory consumption, computational cost and parallelism. Memory consumption defines how much memory is used during the hashing operation and makes parallel hardware attacks expensive. Computational cost indicates the number of iterations the algorithm goes through, while parallelism determines the number of threads working simultaneously. By adjusting these parameters, the algorithm can be tailored to the specific requirements of an application or the available hardware.</p><p>Another advantage of Argon2 is its resistance to modern attacks. The combination of memory and computing-intensive processes makes it difficult for attackers to crack passwords using brute force or specialised hardware. This makes Argon2 ideal for use in safety-critical applications.</p><p>Argon2 is used in many modern cryptography and password management libraries. Developers can implement the algorithm in various programming languages ​​, such as Java, Python, or C.</p><p>In summary, Argon2 is one of the safest password-hashing algorithms. The variant<strong>Argon2id</strong> is especially recommended as a standard for new projects because it provides protection against both side-channel attacks and high attack resistance.</p><h2 id="application-of-argon2-in-java">Application of Argon2 in Java</h2><p>One<strong>Argon2</strong> in<strong>Java</strong> To use it, you can use libraries like<strong>Jargon2</strong> or<strong>Bouncy Castle</strong> because Java does not natively support Argon2. With<strong>Jargon2</strong> , Argon2 is very easy to integrate and use. To do this, first, add the following Maven dependency:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;groupId&gt;</span>com.kosprov<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;artifactId&gt;</span>jargon2-api<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;version&gt;</span>Latest Version Number<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><p>The code to create a hash and verify a password might look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.kosprov.jargon2.api.Jargon2</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Argon2Example</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"DeinSicheresPasswort"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Jargon2</span><span class="p">.</span><span class="na">Hasher</span><span class="w"/><span class="n">hasher</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Jargon2</span><span class="p">.</span><span class="na">jargon2Hasher</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">type</span><span class="p">(</span><span class="n">Jargon2</span><span class="p">.</span><span class="na">Type</span><span class="p">.</span><span class="na">ARGON2id</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">memoryCost</span><span class="p">(</span><span class="n">65536</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">timeCost</span><span class="p">(</span><span class="n">3</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">parallelism</span><span class="p">(</span><span class="n">4</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">saltLength</span><span class="p">(</span><span class="n">16</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">hashLength</span><span class="p">(</span><span class="n">32</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">hashedPassword</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">hasher</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">password</span><span class="p">(</span><span class="n">password</span><span class="p">.</span><span class="na">getBytes</span><span class="p">())</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">encodedHash</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"hashed password: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">hashedPassword</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">matches</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Jargon2</span><span class="p">.</span><span class="na">jargon2Verifier</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">hash</span><span class="p">(</span><span class="n">hashedPassword</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">password</span><span class="p">(</span><span class="n">password</span><span class="p">.</span><span class="na">getBytes</span><span class="p">())</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">verifyEncoded</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"password correct: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">matches</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>If you want to use a more comprehensive cryptography library, you can<strong>use Bouncy Castle</strong>. The corresponding Maven dependency is:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;groupId&gt;</span>org.bouncycastle<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;artifactId&gt;</span>bcprov-jdk18on<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;version&gt;</span>Latest Version Number<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><p>An example of using Argon2 with Bouncy Castle looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.bouncycastle.crypto.params.Argon2Parameters</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.bouncycastle.crypto.generators.Argon2BytesGenerator</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.SecureRandom</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Argon2BouncyCastleExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"securePassword"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">salt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">16</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecureRandom</span><span class="p">().</span><span class="na">nextBytes</span><span class="p">(</span><span class="n">salt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Argon2Parameters</span><span class="p">.</span><span class="na">Builder</span><span class="w"/><span class="n">builder</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Argon2Parameters</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">Builder</span><span class="p">(</span><span class="n">Argon2Parameters</span><span class="p">.</span><span class="na">ARGON2_id</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">withSalt</span><span class="p">(</span><span class="n">salt</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">withMemoryPowOfTwo</span><span class="p">(</span><span class="n">16</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">withParallelism</span><span class="p">(</span><span class="n">4</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">withIterations</span><span class="p">(</span><span class="n">3</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Argon2BytesGenerator</span><span class="w"/><span class="n">generator</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Argon2BytesGenerator</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">generator</span><span class="p">.</span><span class="na">init</span><span class="p">(</span><span class="n">builder</span><span class="p">.</span><span class="na">build</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">hash</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">32</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">generator</span><span class="p">.</span><span class="na">generateBytes</span><span class="p">(</span><span class="n">password</span><span class="p">.</span><span class="na">getBytes</span><span class="p">(),</span><span class="w"/><span class="n">hash</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"hashed password: "</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="n">bytesToHex</span><span class="p">(</span><span class="n">hash</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">bytesToHex</span><span class="p">(</span><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">bytes</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">hexString</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="w"/><span class="n">b</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">bytes</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">hexString</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">"%02x"</span><span class="p">,</span><span class="w"/><span class="n">b</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">hexString</span><span class="p">.</span><span class="na">toString</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Jargon2</strong> was developed specifically for Argon2 and is, therefore, easy to use<strong>Bouncy Castle</strong> for more complex cryptographic requirements. It is recommended in most cases for the pure use of Argon2.</p><h2 id="generate-the-salt-value">Generate the SALT value.</h2><p>So far, it has always been mentioned that producing a good SALT value is important. But how can you do that?</p><p><em>We will examine the topic in detail in the next blog post. Unfortunately, at this point, it would go beyond the scope of this article.</em></p><p>In this blog post, we will examine a basic initial implementation. You will quickly be confronted with an implementation that could look like this.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">generateSalt</span><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">length</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SecureRandom</span><span class="w"/><span class="n">random</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecureRandom</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">salt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">length</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">random</span><span class="p">.</span><span class="na">nextBytes</span><span class="p">(</span><span class="n">salt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Base64</span><span class="p">.</span><span class="na">getEncoder</span><span class="p">().</span><span class="na">encodeToString</span><span class="p">(</span><span class="n">salt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>However, there are some minor comments here.</p><p>Basically, creating a salt with a newly instantiated SecureRandom is not fundamentally wrong in any method, but it increases the chance<strong>that</strong> the same seeds are used at very short intervals or under certain circumstances. In practice, that is<strong>rarely</strong> a problem; however, it is good practice to have a single (static) instance of SecureRandom to be used per application (or per class).</p><p><strong>Why?</strong></p><ul><li>SecureRandom gets its seed (among other things) from the operating system (e.g /dev/urandom in Linux).</li><li>Each re-creation of one SecureRandom can lead to unnecessary system load and, theoretically, minimal increased risk of repetitions.</li><li>At just one SecureRandominstance, a pseudo-random number generator is continued internally, making duplicates very unlikely.</li></ul><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.SecureRandom</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.Base64</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SaltGenerator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">SecureRandom</span><span class="w"/><span class="n">RANDOM</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecureRandom</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">generateSalt</span><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">length</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">salt</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">length</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">RANDOM</span><span class="p">.</span><span class="na">nextBytes</span><span class="p">(</span><span class="n">salt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Base64</span><span class="p">.</span><span class="na">getEncoder</span><span class="p">().</span><span class="na">encodeToString</span><span class="p">(</span><span class="n">salt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This way, (a) is not repeated every time it is called SecureRandom, and (b) reduces the risk of randomly generating identical salts. Of course, purely statistically speaking, collisions can theoretically still occur, but the probability is negligible if the salt length is long enough.</p><p>However, this is only the beginning of the discussion; more on that in the second part.</p><h2 id="what-is-the-procedure-for-checking-the-username-password-combination-during-the-login-process">What is the procedure for checking the username-password combination during the login process?</h2><p>A standardised procedure is followed to check the combination of user name and password during a login process, ensuring that the data is processed securely. First, the user enters their login details via a form transmitted over HTTPS. The server then looks up the username in the database to retrieve relevant information such as password hash and salt. Care should be taken not to reveal whether a user exists.</p><p>The stored password hash is then compared with a recalculated hash of the entered password using the stored salt. If the values ​​match, the login is considered successful; otherwise, a generic error message is returned so as not to reveal additional information.</p><p>After successful verification, a session or JSON Web Token (JWT) is created to maintain the user&rsquo;s authentication. The token does not contain any sensitive information, such as passwords.</p><h2 id="a-few-more-words-about-strings-in-java">A few more words about strings in Java</h2><p>If we imagine that a password is in a text field of a (web) application, we will receive this password as a string. Yes, some will say, but what could be bad about that?</p><p>To do this, we need to examine briefly how strings are handled in the JVM and where attack vectors can lie.</p><p>In Java, strings are immutable, which means that once created, String-Object can no longer be changed. When a string is manipulated through concatenation, a new string object is created in memory while the old one continues to exist until it is removed by the garbage collector (GC). This behaviour can be problematic when sensitive data, such as passwords or cryptographic keys, is stored in strings. Because they cannot be overwritten directly, they may remain in memory longer than necessary and are potentially visible in a memory dump or readable by an attacker. Another problem is that the developer has no control over when the garbage collector removes the sensitive data. This can cause such data to remain in memory for an extended period and possibly become visible in logs or debugging tools.</p><h3 id="so-what-can-you-do-about-it-now">So what can you do about it now?</h3><p>A safer approach is to use this instead of strings char[] because they are changeable, and the memory can be specifically overwritten. This helps minimise the retention time of sensitive data, especially in security-critical applications where memory dumps or debugging tools could provide memory access.</p><p>The advantage of char[] lies in direct memory management: While the garbage collector decides itself when to remove objects, a char[]-Array is explicitly overwritten and thus<strong>immediately</strong> deleted. This significantly reduces the risk of unauthorised access.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.Arrays</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SensitiveDataExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">password</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">{</span><span class="sc">'s'</span><span class="p">,</span><span class="w"/><span class="sc">'e'</span><span class="p">,</span><span class="w"/><span class="sc">'c'</span><span class="p">,</span><span class="w"/><span class="sc">'r'</span><span class="p">,</span><span class="w"/><span class="sc">'e'</span><span class="p">,</span><span class="w"/><span class="sc">'t'</span><span class="p">};</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">processPassword</span><span class="p">(</span><span class="n">password</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Überschreiben des Speichers mit Dummy-Daten</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Arrays</span><span class="p">.</span><span class="na">fill</span><span class="p">(</span><span class="n">password</span><span class="p">,</span><span class="w"/><span class="sc">'\0'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">processPassword</span><span class="p">(</span><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Beispielhafte Verarbeitung</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Passwort verarbeitet: "</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="n">String</span><span class="p">.</span><span class="na">valueOf</span><span class="p">(</span><span class="n">password</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Next to char[], additional security measures should be taken, such as encrypting sensitive data and using SecretKeySpec from the package javax.crypto.spec. for cryptographic keys. This class allows handling cryptographic keys in byte arrays that can be overwritten after use, but I will write a separate blog post about that…</p><h2 id="conclusion">Conclusion</h2><p>Secure storage of passwords is an essential part of every application to protect user data from misuse and unauthorised access. Plain text passwords pose a significant risk and should never be saved directly. Instead, modern hashing algorithms such as PBKDF2, BCrypt or Argon2 must be used to ensure security.</p><p>Brute force and rainbow table attacks illustrate the importance of salts and sufficiently complex hashing mechanisms. The risk of such attacks can be significantly reduced by using random salts and iterative hashing methods. Argon2id, in particular, offers high resistance to modern attack methods due to its storage and computing intensity and is recommended as the preferred solution.</p><p>In addition, passwords within the application should also be processed carefully. Using char[] instead of String to store sensitive data can help prevent unwanted memory leaks. It is also important not to store passwords unsecured in memory or in logs.</p><p>Secure password hashing procedures are a technical best practice and a legal requirement in many areas.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Security</category><media:content url="https://svenruppert.com/images/2025/01/DALL%C2%B7E-2025-01-29-08.38.17-A-steampunk-themed-digital-illustration-depicting-cybersecurity-concepts_-a-brass-and-copper-shield-with-a-key-symbol-in-the-center-surrounded-by-int.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/01/DALL%C2%B7E-2025-01-29-08.38.17-A-steampunk-themed-digital-illustration-depicting-cybersecurity-concepts_-a-brass-and-copper-shield-with-a-key-symbol-in-the-center-surrounded-by-int.jpeg"/><enclosure url="https://svenruppert.com/images/2025/01/DALL%C2%B7E-2025-01-29-08.38.17-A-steampunk-themed-digital-illustration-depicting-cybersecurity-concepts_-a-brass-and-copper-shield-with-a-key-symbol-in-the-center-surrounded-by-int.jpeg" type="image/jpeg" length="0"/></item><item><title>What makes Vaadin components special?</title><link>https://svenruppert.com/posts/what-makes-vaadin-components-special/</link><pubDate>Tue, 19 Aug 2025 13:09:52 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/what-makes-vaadin-components-special/</guid><description>From my experience, Vaadin has always stood out from other Java frameworks. Of course, it enables the creation of modern web UIs, but the real difference lies in its component architecture. This is not conceived as a short-term aid, but is consistently designed for maintainability and flexibility. It creates the possibility of running applications stably for many years while still being able to extend them step by step.</description><content:encoded>&lt;![CDATA[<p>From my experience, Vaadin has always stood out from other Java frameworks. Of course, it enables the creation of modern web UIs, but the real difference lies in its<strong>component architecture</strong>. This is not conceived as a short-term aid, but is consistently designed for maintainability and flexibility. It creates the possibility of running applications stably for many years while still being able to extend them step by step.</p><p>Whether I use a component from the official library, develop my own, or integrate an npm package, Vaadin ensures that all building blocks follow a clear pattern. The connection between server and client remains consistent and reliable. This has allowed me to further develop applications over time without ever being forced to rebuild them entirely from scratch.</p><p><em>Note: This post builds on and reflects further upon the excellent original article by Sami Ekblad, who provided a concise overview of what makes Vaadin components special. My intention here is to expand on those ideas with reflections from my own long-term use in practice. -<a href="https://dev.to/samiekblad/what-makes-vaadin-components-special-9oo-temp-slug-1862339">https://dev.to/samiekblad/what-makes-vaadin-components-special-9oo-temp-slug-1862339</a></em></p><hr><p><strong>More Than Just UI Widgets</strong></p><p>In my practical work, I have observed that libraries such as React or Lit understand their components primarily as frontend building blocks. Vaadin deliberately takes a different approach. For me, the focus is on<strong>longevity and extensibility</strong> , which are crucial in long-term projects. I have seen applications operated with Vaadin for more than a decade, and the underlying component architecture is designed precisely for this.</p><p>What I find particularly interesting is the possibility of implementing UI components either entirely server-side, purely client-side, or in a hybrid form.<strong>This freedom of choice is the real strength</strong> that sets Vaadin apart from traditional UI libraries and gives me the flexibility to choose the most appropriate solution for each use case.</p><hr><p><strong>Three Approaches to Vaadin Components</strong></p><p><strong>1. Server-Side Components</strong></p><p>This is the most traditional route: development is done exclusively in Java. HTML templates or JavaScript are not necessary. With existing building blocks such as TextField, Button, or Grid, entire interfaces can be assembled. These can, in turn, evolve into higher-level components, such as an InvoiceEditor that directly integrates business logic and input validation.</p><figure><img src="/images/2025/08/image.png" alt="" loading="lazy" decoding="async"/><p><a href="/images/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8f18y3acadhm7m8m8q33.png"/></p><p><strong>Strengths:</strong><br>
Server-side components offer maximum control over the validation and security of user input, as all checks take place directly on the server and are thus protected against manipulation. At the same time, there is no dependency on frontend technologies, which reduces complexity and simplifies maintenance. Another advantage is the possibility of generating user interfaces dynamically at runtime, allowing flexible responses to changes in business logic or context. In addition, existing Java libraries and enterprise logic can be integrated seamlessly, creating a smooth transition between UI and backend.</p><p><strong>Weaknesses:</strong><br>
Every user interaction requires a request to the server, which in scenarios with very high interactivity can lead to noticeable delays. In particular, with animations or complex drag-and-drop mechanisms, this approach reaches its limits, as such functions demand an immediate response in the browser that cannot be achieved without client-side logic.</p><hr><p><strong>2. Client-Server Components</strong></p><p>Here, client-side web components (Lit, TypeScript, or Vanilla) are combined with a Vaadin Java API. This architecture unites a rich user experience with secure backend validation.</p><figure><img src="/images/2025/08/image-1.png" alt="" loading="lazy" decoding="async"/><p><a href="/images/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcz03g40pekelt7k43i2l.png"/></p><p><strong>Example:</strong> A date picker with keyboard shortcuts, colour transitions and smooth animations, while still ensuring that inputs are reliably validated on the server.</p><p><strong>Strengths:</strong><br>
Client-server components provide complete control over the DOM and all interactions in the browser. This makes it possible to realise complex and responsive user interfaces that keep pace with modern web standards. At the same time, business logic does not remain unprotected in the frontend but is validated and executed server-side in Java, ensuring a high level of security. Another benefit is the clear separation between UI and server logic, since communication is handled in a structured way by the framework, promoting clarity and maintainability.</p><p><strong>Weaknesses:</strong><br>
A disadvantage is that developers need solid knowledge of both Java and TypeScript. This dual requirement can raise the entry barrier and lengthen the learning curve, since one must be familiar not only with server-side concepts but also with the particularities of client-side development. Especially in teams that have so far focused predominantly on one language, this can demand additional training and a more intensive onboarding process.</p><hr><p><strong>3. Integration of Third-Party Components</strong></p><p>The modern web is rich in high-quality custom elements available via npm. With Vaadin, these elements can be easily integrated into an application. A small amount of TypeScript is sufficient to build the bridge to the Java world.</p><figure><img src="/images/2025/08/image-2.png" alt="" loading="lazy" decoding="async"/><p><a href="/images/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhnle0txlm1ijv3725zxu.png"/></p><p><strong>Strengths:</strong><br>
The integration of existing web components is particularly fast and straightforward, since ready-made elements from the npm ecosystem can be included with minimal effort. Developers benefit from the fact that only a small amount of boilerplate code is required to make a component usable. This leaves more scope for actual application logic. These integrated elements are also well-suited for visual enhancements or additional convenience features that may not be business-critical but still improve the user experience noticeably.</p><p><strong>Weaknesses:</strong><br>
One drawback of integrating third-party components is that server-side validation is not automatically provided and must instead be implemented individually. Developers, therefore, need to add backend logic to ensure that inputs are reliably checked and possible manipulation is prevented. Another point concerns visual design: not all external components integrate seamlessly with the Vaadin design system. This can result in visual inconsistencies that impair a uniform appearance of the application and may require additional adaptation effort.</p><hr><p><strong>Making the Right Choice</strong></p><p>The real question is not to commit permanently to a single approach. Each of the described variants brings specific strengths and weaknesses, which can be relevant in different contexts. In my projects, I have found the most outstanding value when combining the approaches. For example, I have been able to use server-side forms with clear validation logic alongside highly interactive client widgets, while also incorporating external components from the npm ecosystem. In this way, an application emerges in which different building blocks coexist harmoniously without losing their semantic meaning. At its core, a button remains a button – regardless of whether it is implemented server-side, client-side, or in a hybrid form.</p><hr><p><strong>Conclusion: Flexibility as a Guiding Principle</strong></p><p>In my experience, Vaadin’s true strength does not lie solely in its library or the choice between Java and TypeScript, but in the<strong>flexibility</strong> that allows me as a developer to place logic exactly where it delivers the most significant benefit in terms of performance, security and maintainability. Particularly useful is the fact that existing components can be combined seamlessly with new technical approaches, enabling applications to grow continuously without requiring a fundamental redevelopment. This possibility of evolving systems organically has proved to be a decisive advantage in long-term projects.</p><p>For me, Vaadin components are therefore far more than simple UI building blocks. They embody an architectural principle designed for sustainability and future viability. This creates a stable foundation on which modern user interfaces can be developed that adapt flexibly to new requirements. In my work, I have repeatedly found that this approach allows new functionality to be introduced step by step without risking breaks in the existing architecture or being forced to restart a project from scratch.</p><hr><p>In conclusion, what have your experiences been with the different approaches? Have you already worked with hybrid components that combine server-side and client-side logic, or have you instead found situations where a consistently pursued style has brought advantages? I am particularly interested in how you use these possibilities in practice, what challenges you have encountered, and which patterns have proved helpful for you.</p>
]]></content:encoded><category>Java</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/08/ChatGPT-Image-19.-Aug.-2025-13_02_16.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/08/ChatGPT-Image-19.-Aug.-2025-13_02_16.png"/><enclosure url="https://svenruppert.com/images/2025/08/ChatGPT-Image-19.-Aug.-2025-13_02_16.png" type="image/jpeg" length="0"/></item><item><title>Part III - WebUI with Vaadin Flow for the URL Shortener</title><link>https://svenruppert.com/posts/part-iii-webui-with-vaadin-flow-for-the-url-shortener/</link><pubDate>Fri, 15 Aug 2025 10:46:10 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/part-iii-webui-with-vaadin-flow-for-the-url-shortener/</guid><description>1. Introduction and objectives The first two parts of this series established the theoretical and practical foundations of a URL shortener in pure Java. We discussed the semantic classification of short URLs, the architecture of a robust mapping system, and the implementation of a REST-based service based on the JDK HTTP server. These efforts resulted in a functional, modularly extensible backend that creates, manages, and efficiently resolves short links. However, a crucial component was missing-a visual interface for direct user interaction with the system. This interface is essential for tasks such as manual link creation, viewing existing mappings, and analysing individual redirects.</description><content:encoded>&lt;![CDATA[<h3 id="1-introduction-and-objectives">1. Introduction and objectives</h3><p>The first two parts of this series established the theoretical and practical foundations of a URL shortener in pure Java. We discussed the semantic classification of short URLs, the architecture of a robust mapping system, and the implementation of a REST-based service based on the JDK HTTP server. These efforts resulted in a functional, modularly extensible backend that creates, manages, and efficiently resolves short links. However, a crucial component was missing-a visual interface for direct user interaction with the system. This interface is essential for tasks such as manual link creation, viewing existing mappings, and analysing individual redirects.</p><ol><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#1-introduction-and-objectives">1. Introduction and objectives</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#2-ui-strategy-and-technology-choice">2. UI strategy and technology choice</a><ol><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#2-1-why-vaadin-flow-for-a-server-side-interface">2.1 Why Vaadin Flow for a server-side interface?</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#2-2-ui-paradigms-for-administration-systems">2.2 UI paradigms for administration systems</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#2-3-differentiation-from-client-side-frameworks">2.3 Differentiation from client-side frameworks</a></li></ol></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#3-user-interface-architecture">3. User interface architecture</a><ol><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#3-1-integration-into-the-existing-war-structure">3.1 Integration into the existing WAR structure</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#3-2-separation-of-routing-views-and-services">3.2 Separation of routing, views and services</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#3-3-communication-with-the-core-module-di-without-frameworks">3.3 Communication with the core module (DI without frameworks)</a></li></ol></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#4-user-guidance-and-ux-design">4. User guidance and UX design</a><ol><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#4-1-navigation-concept-dashboard-create-overview">4.1 Navigation concept: Dashboard, Create, Overview</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#4-2-input-validation-and-feedback">4.2 Input validation and feedback</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#4-3-readability-design-system-and-accessibility">4.3 Readability, design system and accessibility</a></li></ol></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#5-implementation-of-the-ui-components-with-vaadin-flow-including-source-code">5. Implementation of the UI components with Vaadin Flow (including source code)</a><ol><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#5-1-mainlayout-navigation-frame-and-routing">5.1 MainLayout: Navigation frame and routing</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#5-2-createview-input-form-with-validation">5.2 CreateView: Input form with validation</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#5-3-overviewview-grid-based-management-view">5.3 OverviewView: Grid-based management view</a></li></ol></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#5-4-consistent-behaviour-for-dialogues-and-notifications-with-code">5.4 Consistent behaviour for dialogues and notifications (with code)</a><ol><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#success-stories-via-notification">Success stories via Notification</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#error-messages-through-field-based-validation-withbinder">Error messages through field-based validation withBinder</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#destructive-actions-only-after-explicit-confirmation">Destructive actions only after explicit confirmation</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#interaction-and-reuse">Interaction and reuse</a></li></ol></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#7-deploying-the-ui-in-the-war-context">7. Deploying the UI in the WAR context</a><ol><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#7-1-servlet-configuration-for-vaadin-inweb-xml">7.1 Servlet configuration for Vaadin inweb.xml</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#7-2-theme-integration-ohne-frontend-toolchain">7.2 Theme-Integration ohne Frontend-Toolchain</a></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#7-3-operation-in-tomcat-or-jetty">7.3 Operation in Tomcat or Jetty</a></li></ol></li><li><a href="https://svenruppert.com/2025/08/15/auto-draft/#8-conclusion-and-outlook">8. Conclusion and outlook</a></li></ol><p>This is precisely where the third part of this series comes in. The focus is on the development of a graphical user interface based on<strong>Vaadin Flow.</strong> The goal is to provide a web interface developed entirely in Java that eliminates the need for client-side frameworks and integrates seamlessly into the application&rsquo;s existing WAR structure. The decision to use Vaadin Flow follows a pragmatic principle: Anyone who already writes Java should also be able to define interfaces in Java, with a type-safe layout, a declarative component model, and a clear separation between the UI and domain layers.</p><p>UI integration in our project serves not only to enhance functionality but also to achieve a didactic objective. In security-critical applications like URL shorteners, human interaction is key. A well-designed UI not only facilitates convenient operation but also prevents common errors and misuse through appropriate validation, clear feedback, and restrictive permissions concepts. This makes the user interface itself a security-relevant part of the architecture.</p><p>In the remainder of this article, we will examine the structure, design, and concrete implementation of the Vaadin interface in detail. This ranges from the module architecture to navigation and view components, including advanced features such as QR code generation and expiration time display. The goal remains to implement all functionality exclusively using Java&rsquo;s built-in tools—in the spirit of a transparent, maintainable, and extensible system design.</p><p>The UI is not just an add-on, but a central component of a comprehensive shortener solution. It caters to both productive use and teaching and exploration, providing a sense of security and reliability.</p><h3 id="2-ui-strategy-and-technology-choice">2. UI strategy and technology choice</h3><p>Choosing the right technology for the user interface of a technical service is far more than a matter of taste – it touches on key aspects such as maintainability, security model, development efficiency, and deployment strategy. In the context of our URL shortener, which deliberately avoids external frameworks and was implemented entirely in Java 24, it made sense to choose a solution for the UI that fits seamlessly into this philosophy. The decision was therefore made to use<strong>Vaadin Flow</strong> , a server-side UI framework powered and developed entirely in Java.</p><h4 id="21-why-vaadin-flow-for-a-server-side-interface">2.1 Why Vaadin Flow for a server-side interface?</h4><p>Vaadin Flow enables the development of component-based web applications in Java, without the need for JavaScript, front-end build processes, or manual maintenance of HTML, CSS, or TypeScript artefacts. All UI components, from simple input fields to complex tables, are described declaratively in Java, allowing the UI to seamlessly integrate into the existing modular structure. For a purely Java-based backend logic like our URL shortener, this results in a coherent overall architecture that eliminates the need for language or context switching, instilling confidence in its efficiency.</p><p>The logic is executed entirely server-side: User interactions trigger events that are processed on the server, with the presentation in the browser synchronised via the Vaadin client. This architecture not only simplifies state management but also reduces potential attack surfaces, as no server-side business logic migrates to the frontend.</p><h4 id="22-ui-paradigms-for-administration-systems">2.2 UI paradigms for administration systems</h4><p>When designing an admin interface, it&rsquo;s not about aesthetic brilliance, but rather functional clarity, robust interactions, and data accountability. Users of a URL shortener—whether self-hosted, in educational institutions, or within an organisation—need an easy way to manually shorten new URLs, browse existing mappings, check expiration times, and delete potentially critical entries. These functions must be visible, traceable, and cancelable at any time.</p><p>Vaadin Flow offers a variety of ready-made components, such as<strong>Grid</strong> ,<strong>FormLayout</strong> ,<strong>Dialogue</strong> ,<strong>Notification</strong> or<strong>Binder</strong> that enable efficient implementation of such use cases – including validation, event control, and dynamic data binding. This not only saves development time but also enables a consistent user experience that scales elegantly with increasing complexity.</p><h4 id="23-differentiation-from-client-side-frameworks">2.3 Differentiation from client-side frameworks</h4><p>Unlike single-page application frameworks such as Angular, React or Vue, Vaadin Flow requires<strong>no separate frontend build pipeline</strong> , no TypeScript toolchains, and no JSON serialisation of server communication. The entire application, including the UI, can be deployed as a<strong>classic WAR</strong> Package and run in a servlet container such as Jetty or Tomcat. This not only reduces DevOps effort but also the risk of version conflicts, CDN outages, or cross-origin issues.</p><p>This server-side approach is particularly suitable for applications where<strong>Data sovereignty, security and long-term maintainability</strong> are in the foreground – for example, with internal tools, admin consoles, or security-critical services. The integration of session-based security mechanisms, IP filters, or role-based access is also much more controlled in a Vaadin servlet than with headless backends with an external JavaScript frontend.</p><p>The choice of Vaadin Flow is therefore not just a technological decision, but an expression of an architectural style:<strong>stable, server-centric, Java-enabled</strong> – ideal for secure, maintainable and explainable systems.</p><h3 id="3-user-interface-architecture">3. User interface architecture</h3><p>The user interface is not an isolated component, but an integral part of the system. It must fit seamlessly into the existing module architecture without enforcing domain-specific dependencies or abandoning the loose coupling between core logic and application layers. In a purely Java-based environment without frameworks such as Spring or Jakarta EE, this separation is particularly important because it allows the UI to be viewed as a separate layer, both technically and semantically.</p><h4 id="31-integration-into-the-existing-war-structure">3.1 Integration into the existing WAR structure</h4><p>The application is already organised as a multi-tiered Maven project, which is consolidated into a central WAR module. This WAR module knows the servlet context, initialises the HTTP server (for REST) and the VaadinServlet (for the UI), and thus takes on the role of Integration and delivery unit. The previous modules<strong>shortener-core</strong>(domain) and<strong>shortener-api</strong>(HTTP routing) remain unaffected.</p><p>The new UI module –<strong>shortener-ui-required</strong> – is implemented as an additional Maven module, which is used exclusively by shortener-core. It contains all Vaadin-specific logic, UI components, and views. In the WAR module, this module is then connected via a servlet registration – typically via a Servlet, which is defined in the deployment descriptor (web.xml) or via ServletContainerInitializer- and is integrated.</p><p>The structure follows the principle:<em>Only the WAR module knows the specific deployment topology. All other modules remain generic and independent.</em></p><h4 id="32-separation-of-routing-views-and-services">3.2 Separation of routing, views and services</h4><p>Within the UI module, responsibilities are divided:</p><ul><li><p><strong>Routing</strong> and<strong>Navigation</strong> are centrally controlled via a MainLayout class and with @Route-annotated views. These define the paths within the UI (e.g./, /create, /admin) and automatically take over session and history management from Vaadin.</p></li><li><p><strong>Views</strong> (approximately CreateView, OverviewView, StatsView) form the interactive interfaces. They integrate Vaadin components such as TextField, Grid, Button, Dialog and provide user interaction. Each view is stateless and delegates to services.</p></li><li><p><strong>Services</strong> encapsulate the interaction with the business logic. These are not typical Spring beans, but regular Java classes that can be implemented either directly or via the Singleton pattern (ServiceRegistry, StaticFactory), and are instantiated. They call methods shortener-core and take care of conversion, validation and error handling.</p></li></ul><p>This structure allows for a clear separation between presentation, application logic, and business logic. It is explicitly based on classic UI architectural patterns such as Model-View-Service (MVS), without the overhead or magic of full-fledged frameworks.</p><h4 id="33-communication-with-the-core-module-di-without-frameworks">3.3 Communication with the core module (DI without frameworks)</h4><p>Since dependency injection containers are not used, the views are deployed with service instances manually or via a central deployment class. A typical approach is to implement a<strong>UiServiceLocator</strong> , which contains all the required components (e.g.UrlMappingStore, ShortCodeGenerator), is created once and then made available as a singleton.</p><p>For example, the constructor of a view can look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">OverviewView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">UrlMappingStore</span><span class="w"/><span class="n">store</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UiServiceLocator</span><span class="p">.</span><span class="na">getUrlMappingStore</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">public</span><span class="w"/><span class="nf">OverviewView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Build components, display data, define actions</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This form of explicit dependency creation has the advantage that the origin of all components remains traceable, no hidden initialisation takes place, and the entire application context remains transparently controllable – a property that is particularly important in safety-critical systems.</p><h3 id="4-user-guidance-and-ux-design">4. User guidance and UX design</h3><p>An administration interface is not a classic front end for end users, but a specialised tool – comparable to a precision instrument. Its user interface must be efficient, robust, and explainable. In contrast to dynamic consumer UIs, which boast animations, branding, or complex interactions, clarity, predictability, and freedom from redundancy are paramount here. The design of a Vaadin interface for a URL shortener, therefore, follows a minimalist, function-centric UX strategy.</p><h4 id="41-navigation-concept-dashboard-create-overview">4.1 Navigation concept: Dashboard, Create, Overview</h4><p>From the user’s perspective, the application is divided into three central task areas:</p><ul><li><strong>Creating new short links</strong> : A simple input form with target URL validation and an optional custom alias. Speed and clarity are paramount.</li><li><strong>Overview of existing mappings</strong> : A tabular display of all generated shortlinks, supplemented with sorting and filtering options. This allows for targeted searches by alias, target address, or expiration date.</li><li><strong>Administrative Dashboard</strong> : An area for viewing metrics (e.g. number of mappings, frequently used short links) as well as for targeted deletion or extension of entries.</li></ul><p>Navigation is done via a Vaadin layout with a main menu (e.g.<strong>AppLayout</strong> or<strong>DrawerToggle</strong>) and named routes. All routes are directly accessible via URL, with bookmarks and refreshes always leading to a consistent state—an inherent advantage of Vaadin Flow due to server-side session management and declarative routing.</p><h4 id="42-input-validation-and-feedback">4.2 Input validation and feedback</h4><p>A key UX criterion is the correctness of the inputs. When creating a new mapping, it must be ensured that</p><ul><li>The input field for the URL is not empty,</li><li>The URL is syntactically valid (including http/https),</li><li>optional aliases are not already assigned or reserved.</li></ul><p>These validations are performed immediately upon input (client-side via Binder) and again server-side in the service layer, whereby the view displays corresponding error messages as<strong>Notification</strong> ,<strong>ErrorMessage</strong> or directly in the field context. A deliberate UX detail: In the event of errors, the focus remains on the field, the previous input is not deleted, and a visual highlight appears, instead of a general error dialogue.</p><p>Successful processes (e.g., successfully created a short link) lead to clear success messages, including immediate copying options, e.g., via a button with an icon (&ldquo;copy to clipboard&rdquo;) to support cross-media use.</p><h4 id="43-readability-design-system-and-accessibility">4.3 Readability, design system and accessibility</h4><p>The UI is functional, not decorative. Accordingly, attention is paid to a clear visual grid: sufficient white space, consistent labels, understandable icons, and no unnecessary animations. Colours serve solely for semantic marking (e.g., red for deletion actions, green for success messages), not for stylistic individualisation.</p><p>Vaadin Flow comes with a solid standard design that extends<strong>@Theme,</strong> or custom CSS classes can be extended, but only where functionally necessary. The entire application remains fully keyboard-operated, screen reader-compatible, and high-contrast enough for low-barrier use. This is especially important in contexts where administrators work in security-critical areas with increased accessibility requirements.</p><h3 id="5-implementation-of-the-ui-components-with-vaadin-flow-including-source-code">5. Implementation of the UI components with Vaadin Flow (including source code)</h3><h4 id="51-mainlayout-navigation-frame-and-routing">5.1 MainLayout: Navigation frame and routing</h4><p>The MainLayout forms the common structure of all views. It is based on the AppLayout and defines a vertical navigation menu with links to the central views:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Theme</span><span class="p">(</span><span class="s">"shortener-theme"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainLayout</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">AppLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">MainLayout</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">DrawerToggle</span><span class="w"/><span class="n">toggle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DrawerToggle</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">H1</span><span class="w"/><span class="n">title</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">H1</span><span class="p">(</span><span class="s">"URL Shortener Admin"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">title</span><span class="p">.</span><span class="na">getStyle</span><span class="p">().</span><span class="na">set</span><span class="p">(</span><span class="s">"font-size"</span><span class="p">,</span><span class="w"/><span class="s">"var(--lumo-font-size-l)"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                         </span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="s">"margin"</span><span class="p">,</span><span class="w"/><span class="s">"0"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">HorizontalLayout</span><span class="w"/><span class="n">header</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">toggle</span><span class="p">,</span><span class="w"/><span class="n">title</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">header</span><span class="p">.</span><span class="na">setDefaultVerticalComponentAlignment</span><span class="p">(</span><span class="n">Alignment</span><span class="p">.</span><span class="na">CENTER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">header</span><span class="p">.</span><span class="na">setWidthFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">header</span><span class="p">.</span><span class="na">setPadding</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">addToNavbar</span><span class="p">(</span><span class="n">header</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">RouterLink</span><span class="w"/><span class="n">createLink</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RouterLink</span><span class="p">(</span><span class="s">"Erstellen"</span><span class="p">,</span><span class="w"/><span class="n">CreateView</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">RouterLink</span><span class="w"/><span class="n">overviewLink</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RouterLink</span><span class="p">(</span><span class="s">"Übersicht"</span><span class="p">,</span><span class="w"/><span class="n">OverviewView</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">VerticalLayout</span><span class="w"/><span class="n">menu</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">VerticalLayout</span><span class="p">(</span><span class="n">createLink</span><span class="p">,</span><span class="w"/><span class="n">overviewLink</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">menu</span><span class="p">.</span><span class="na">setPadding</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">menu</span><span class="p">.</span><span class="na">setSpacing</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">menu</span><span class="p">.</span><span class="na">setSizeFull</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">addToDrawer</span><span class="p">(</span><span class="n">menu</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This structure separates navigation logic from application logic. New views only need to be<strong>@Route(&hellip;, layout = MainLayout.class)</strong> and connected.</p><h4 id="52-createview-input-form-with-validation">5.2 CreateView: Input form with validation</h4><p>The view for creating new short links consists of a TextField for the target URL, an optional alias field, and a button for generating the mapping. Simple validation logic is provided via the binder:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Route(value = "", layout = MainLayout.class)</span></span><span class="line"><span class="cl">public class CreateView extends VerticalLayout {</span></span><span class="line"><span class="cl">    private final UrlMappingStore store = UiServiceLocator.getUrlMappingStore();</span></span><span class="line"><span class="cl">    public CreateView() {</span></span><span class="line"><span class="cl">        setSpacing(true);</span></span><span class="line"><span class="cl">        setPadding(true);</span></span><span class="line"><span class="cl">TextField urlField = new TextField("Target URL");</span></span><span class="line"><span class="cl">        urlField.setWidthFull();</span></span><span class="line"><span class="cl">        TextField aliasField = new TextField("Alias (optional)");</span></span><span class="line"><span class="cl">        aliasField.setWidth("300px");</span></span><span class="line"><span class="cl">Button shortenButton = new Button("Shorten");</span></span><span class="line"><span class="cl">        shortenButton.addThemeVariants(ButtonVariant.LUMO_PRIMARY);</span></span><span class="line"><span class="cl">        HorizontalLayout actions = new HorizontalLayout(shortenButton);</span></span><span class="line"><span class="cl">        actions.setAlignItems(Alignment.END);</span></span><span class="line"><span class="cl">        Binder<span class="nt">&lt;ShortenRequest&gt;</span> binder = new Binder<span class="err">&lt;</span>&gt;(ShortenRequest.class);</span></span><span class="line"><span class="cl">        ShortenRequest request = new ShortenRequest();</span></span><span class="line"><span class="cl">        binder.forField(urlField)</span></span><span class="line"><span class="cl">.asRequired("URL must not be empty")</span></span><span class="line"><span class="cl">              .withValidator(url -&gt; url.startsWith("http://") || url.startsWith("https://"), </span></span><span class="line"><span class="cl">"Only HTTP(S) URLs allowed")</span></span><span class="line"><span class="cl">              .bind(ShortenRequest::url, ShortenRequest::url);</span></span><span class="line"><span class="cl">        binder.forField(aliasField)</span></span><span class="line"><span class="cl">              .bind(ShortenRequest::alias, ShortenRequest::alias);</span></span><span class="line"><span class="cl">        shortenButton.addClickListener(click -&gt; {</span></span><span class="line"><span class="cl">            if (binder.writeBeanIfValid(request)) {</span></span><span class="line"><span class="cl">                Optional<span class="nt">&lt;String&gt;</span> code = createShortCode(request);</span></span><span class="line"><span class="cl">                code.ifPresentOrElse(c -&gt; {</span></span><span class="line"><span class="cl">Notification.show("Short link created: " + c);</span></span><span class="line"><span class="cl">                    urlField.clear();</span></span><span class="line"><span class="cl">                    aliasField.clear();</span></span><span class="line"><span class="cl">}, () -&gt; Notification.show("Alias already assigned or error saving", 3000, Position.MIDDLE));</span></span><span class="line"><span class="cl">            }</span></span><span class="line"><span class="cl">        });</span></span><span class="line"><span class="cl">add(new H2("Create new short link"), urlField, aliasField, actions);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    private Optional<span class="nt">&lt;String&gt;</span> createShortCode(ShortenRequest req) {</span></span><span class="line"><span class="cl">        try {</span></span><span class="line"><span class="cl">            return Optional.ofNullable(</span></span><span class="line"><span class="cl">                req.alias() == null || req.alias().isBlank()</span></span><span class="line"><span class="cl">                ? store.createMapping(req.url()).shortCode()</span></span><span class="line"><span class="cl">                : store.createCustomMapping(req.alias(), req.url()).shortCode()</span></span><span class="line"><span class="cl">            );</span></span><span class="line"><span class="cl">        } catch (IllegalArgumentException e) {</span></span><span class="line"><span class="cl">            return Optional.empty();</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    private record ShortenRequest(String url, String alias) {</span></span><span class="line"><span class="cl">        public ShortenRequest() { this("", ""); }</span></span><span class="line"><span class="cl">        public String url() { return url; }</span></span><span class="line"><span class="cl">        public String alias() { return alias; }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This view encapsulates the entire user interaction for creating new short links. Incorrect entries result in immediate feedback, while valid entries are persisted and output directly as feedback.</p><h4 id="53-overviewview-grid-based-management-view">5.3 OverviewView: Grid-based management view</h4><p>The OverviewView displays all existing mappings in the grid. Optionally, additional columns such as creation time or expiration date can be displayed. Each row allows deletion via a confirmation dialogue:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Route(value = "overview", layout = MainLayout.class)</span></span><span class="line"><span class="cl">public class OverviewView extends VerticalLayout {</span></span><span class="line"><span class="cl">    private final UrlMappingStore store = UiServiceLocator.getUrlMappingStore();</span></span><span class="line"><span class="cl">    private final Grid<span class="nt">&lt;ShortUrlMapping&gt;</span> grid = new Grid<span class="err">&lt;</span>&gt;(ShortUrlMapping.class, false);</span></span><span class="line"><span class="cl">    public OverviewView() {</span></span><span class="line"><span class="cl">        setSizeFull();</span></span><span class="line"><span class="cl">        setPadding(true);</span></span><span class="line"><span class="cl">        setSpacing(true);</span></span><span class="line"><span class="cl">        grid.addColumn(ShortUrlMapping::shortCode).setHeader("Short Code");</span></span><span class="line"><span class="cl">        grid.addColumn(ShortUrlMapping::originalUrl).setHeader("Original URL").setFlexGrow(1);</span></span><span class="line"><span class="cl">        grid.addColumn(m -&gt; m.createdAt().toString()).setHeader("Erstellt am");</span></span><span class="line"><span class="cl">        grid.addComponentColumn(this::buildActionButtons).setHeader("Aktionen");</span></span><span class="line"><span class="cl">        grid.setItems(store.findAll());</span></span><span class="line"><span class="cl">        grid.setSizeFull();</span></span><span class="line"><span class="cl">        add(new H2("Alle Kurzlinks"), grid);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    private Component buildActionButtons(ShortUrlMapping mapping) {</span></span><span class="line"><span class="cl">Button delete = new Button("Delete", e -&gt; openConfirmDialog(mapping));</span></span><span class="line"><span class="cl">        delete.addThemeVariants(ButtonVariant.LUMO_ERROR);</span></span><span class="line"><span class="cl">        return delete;</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    private void openConfirmDialog(ShortUrlMapping mapping) {</span></span><span class="line"><span class="cl">        Dialog dialog = new Dialog();</span></span><span class="line"><span class="cl">dialog.add(new Text("Really delete short link? (" + mapping.shortCode() + ")"));</span></span><span class="line"><span class="cl">        Button confirm = new Button("Ja", e -&gt; {</span></span><span class="line"><span class="cl">            store.delete(mapping.shortCode());</span></span><span class="line"><span class="cl">            grid.setItems(store.findAll());</span></span><span class="line"><span class="cl">            dialog.close();</span></span><span class="line"><span class="cl">Notification.show("Short link deleted.");</span></span><span class="line"><span class="cl">        });</span></span><span class="line"><span class="cl">        confirm.addThemeVariants(ButtonVariant.LUMO_PRIMARY);</span></span><span class="line"><span class="cl">        Button cancel = new Button("Cancel", e -&gt; dialog.close());</span></span><span class="line"><span class="cl">        dialog.add(new HorizontalLayout(confirm, cancel));</span></span><span class="line"><span class="cl">        dialog.open();</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The grid is updated automatically after deletions. Additional filter or export functions can be easily added based on this structure.</p><h3 id="54-consistent-behaviour-for-dialogues-and-notifications-with-code">5.4 Consistent behaviour for dialogues and notifications (with code)</h3><p>In an administration interface for a security-critical service such as a URL shortener, the way the system handles user interactions is crucial. The response to inputs—be they correct, incorrect, or critical—must be<strong>predictable, explainable and context-appropriate.</strong> Particularly important are:</p><ul><li>immediate,<strong>non-blocking success messages</strong> for valid actions,</li><li><strong>precise, field-oriented fault indications</strong> in case of incorrect entries,</li><li><strong>intention-confirming dialogues</strong> for potentially destructive operations such as deleting mappings.</li></ul><p>These three feedback types can be implemented entirely using Vaadin Flow&rsquo;s built-in tools – specifically: notification, dialogue, and component binding via Binder.</p><h4 id="success-stories-via-notification">Success stories via Notification</h4><p>After successful actions (e.g., creating a short link or deleting an entry), a discreet, automatically disappearing notification is displayed. This is non-blocking, clearly informs users of the success, and allows them to continue working seamlessly.</p><p><strong>Notification.show(&ldquo;Short link created successfully.&rdquo;, 3000, Notification.Position.TOP_CENTER);</strong></p><p>For targeted feedback with content (e.g. the generated short code), an inline component or a copyable text block can be displayed alternatively:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">kotlin</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-kotlin" data-lang="kotlin"><span class="line"><span class="cl"><span class="k">var</span><span class="py">shortCodeField</span><span class="p">=</span><span class="n">new</span><span class="n">TextField</span><span class="p">(</span><span class="s2">"Your short link"</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">shortCodeField</span><span class="p">.</span><span class="n">setValue</span><span class="p">(</span><span class="s2">"https://short.ly/"</span><span class="p">+</span><span class="n">code</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">shortCodeField</span><span class="p">.</span><span class="n">setReadOnly</span><span class="p">(</span><span class="k">true</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="n">shortCodeField</span><span class="p">.</span><span class="n">setWidthFull</span><span class="p">();</span></span></span><span class="line"><span class="cl"><span class="n">add</span><span class="p">(</span><span class="n">shortCodeField</span><span class="p">);</span></span></span><span class="line"><span class="cl"><span class="nc">Notification</span><span class="p">.</span><span class="n">show</span><span class="p">(</span><span class="s2">"You can now copy the link."</span><span class="p">);</span></span></span></code></pre></div></div><h4 id="error-messages-through-field-based-validation-withbinder">Error messages through field-based validation withBinder</h4><p>Failed inputs—such as invalid URLs or duplicate aliases—do not need to be displayed in general dialogues, but rather directly in the affected field. To do this, Vaadin uses the Binder<T> mechanism, which binds validation rules directly to UI components:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Binder<span class="nt">&lt;ShortenRequest&gt;</span> binder = new Binder<span class="err">&lt;</span>&gt;(ShortenRequest.class);</span></span><span class="line"><span class="cl">binder.forField(urlField)</span></span><span class="line"><span class="cl">.asRequired("URL must not be empty")</span></span><span class="line"><span class="cl">.withValidator(this::isValidHttpUrl, "Only valid HTTP(S) URLs allowed")</span></span><span class="line"><span class="cl">    .bind(ShortenRequest::url, ShortenRequest::url);</span></span></code></pre></div></div><p>The method isValidHttpUrl checks, for example, with a simple Regex or<strong>URI.create(&hellip;)</strong> Logic for syntactical validity. Errors are automatically displayed as error messages directly below the field, visually highlighted and focused:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isValidHttpUrl</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">url</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"/><span class="n">url</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="p">(</span><span class="n">url</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="s">"http://"</span><span class="p">)</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">url</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="s">"https://"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>If an error occurs at the application level (e.g. alias already assigned), this can also be communicated via a global notification:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Alias already taken"</span><span class="p">,</span><span class="w"/><span class="n">3000</span><span class="p">,</span><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">Position</span><span class="p">.</span><span class="na">MIDDLE</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">NotificationVariant</span><span class="p">.</span><span class="na">LUMO_ERROR</span><span class="p">);</span></span></span></code></pre></div></div><h4 id="destructive-actions-only-after-explicit-confirmation">Destructive actions only after explicit confirmation</h4><p>When deleting a short link, resetting a counter, or performing similar irreversible actions, the operation must never be performed without confirmation. Vaadin&rsquo;s dialogue component, combined with descriptive text and two explicit buttons, is ideal for this.</p><p>Example from the OverviewView:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">openConfirmDialog</span><span class="p">(</span><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">mapping</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Dialog</span><span class="w"/><span class="n">dialog</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Dialog</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">dialog</span><span class="p">.</span><span class="na">setHeaderTitle</span><span class="p">(</span><span class="s">"Confirm deletion"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Span</span><span class="w"/><span class="n">message</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="s">"Do you really want to delete the short link? ("</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">mapping</span><span class="p">.</span><span class="na">shortCode</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">")"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">dialog</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">message</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">confirm</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Delete"</span><span class="p">,</span><span class="w"/><span class="n">click</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">store</span><span class="p">.</span><span class="na">delete</span><span class="p">(</span><span class="n">mapping</span><span class="p">.</span><span class="na">shortCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">grid</span><span class="p">.</span><span class="na">setItems</span><span class="p">(</span><span class="n">store</span><span class="p">.</span><span class="na">findAll</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Short link deleted."</span><span class="p">,</span><span class="w"/><span class="n">3000</span><span class="p">,</span><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">Position</span><span class="p">.</span><span class="na">TOP_CENTER</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">NotificationVariant</span><span class="p">.</span><span class="na">LUMO_SUCCESS</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Button</span><span class="w"/><span class="n">cancel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Abbrechen"</span><span class="p">,</span><span class="w"/><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">dialog</span><span class="p">.</span><span class="na">close</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">cancel</span><span class="p">.</span><span class="na">addThemeVariants</span><span class="p">(</span><span class="n">ButtonVariant</span><span class="p">.</span><span class="na">LUMO_TERTIARY</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">dialog</span><span class="p">.</span><span class="na">getFooter</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="n">cancel</span><span class="p">,</span><span class="w"/><span class="n">confirm</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">dialog</span><span class="p">.</span><span class="na">open</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Features of this implementation:</strong></p><ul><li>An explicit button triggers the action.</li><li>Confirmation is dialogue-based with a cancel option.</li><li>The change will be reflected immediately upon completion.</li><li>Feedback is provided immediately via notification.</li></ul><h4 id="interaction-and-reuse">Interaction and reuse</h4><p>A central UX pattern of this interface is<strong>to standardise the behaviour of all interactions.</strong> This avoids surprises for users. Consistent behaviour means:</p><table><thead><tr><th><strong>action</strong></th><th><strong>Reaction</strong></th></tr></thead><tbody><tr><td>URL successfully shortened</td><td>Notification, display of the short link</td></tr><tr><td>Incorrect input</td><td>Field marking, inline error message</td></tr><tr><td>Alias already taken</td><td>central notification with incorrect colouring</td></tr><tr><td>Delete the short link</td><td>Dialogue with two buttons</td></tr><tr><td>Confirmed deletion</td><td>Notification: Grid is reloading</td></tr></tbody></table><p>These patterns can be encapsulated component-based – for example, using utility methods for dialogue generation or notification factories, which can later also be adapted for logging or internationalisation.</p><h3 id="7-deploying-the-ui-in-the-war-context">7. Deploying the UI in the WAR context</h3><p>The user interface is not delivered as a separate application, but integrated into a central WAR file that contains both the REST API and the Vaadin-based UI. This monolithic approach is deliberately chosen: It enables a consistent deployment model on servlet containers such as<strong>Jetty</strong> ,<strong>Tomcat</strong> or<strong>Undertow</strong> without relying on complex build pipelines, containerization, or framework integration.</p><h4 id="71-servlet-configuration-for-vaadin-inwebxml">7.1 Servlet configuration for Vaadin inweb.xml</h4><p>Vaadin Flow is traditionally registered via the servlet com.vaadin.flow.server.VaadinServlet. Since there is no automatic configuration (as with Spring Boot), the assignment is made explicitly in the deployment descriptor:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;web-app</span><span class="na">xmlns=</span><span class="s">"http://xmlns.jcp.org/xml/ns/javaee"</span></span></span><span class="line"><span class="cl">         <span class="na">version=</span><span class="s">"4.0"</span><span class="nt">&gt;</span></span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- I require UI --&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;servlet&gt;</span></span></span><span class="line"><span class="cl">        <span class="nt">&lt;servlet-name&gt;</span>VaadinServlet<span class="nt">&lt;/servlet-name&gt;</span></span></span><span class="line"><span class="cl">        <span class="nt">&lt;servlet-class&gt;</span>com.vaadin.flow.server.VaadinServlet<span class="nt">&lt;/servlet-class&gt;</span></span></span><span class="line"><span class="cl">        <span class="nt">&lt;heat-param&gt;</span></span></span><span class="line"><span class="cl">            <span class="nt">&lt;param-name&gt;</span>ui<span class="nt">&lt;/param-name&gt;</span></span></span><span class="line"><span class="cl">            <span class="nt">&lt;param-value&gt;</span>shortener.ui.MainUI<span class="nt">&lt;/param-value&gt;</span></span></span><span class="line"><span class="cl">        <span class="nt">&lt;/init-param&gt;</span></span></span><span class="line"><span class="cl">        <span class="nt">&lt;load-on-startup&gt;</span>1<span class="nt">&lt;/load-on-startup&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;/servlet&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;servlet-mapping&gt;</span></span></span><span class="line"><span class="cl">        <span class="nt">&lt;servlet-name&gt;</span>VaadinServlet<span class="nt">&lt;/servlet-name&gt;</span></span></span><span class="line"><span class="cl">        <span class="nt">&lt;url-pattern&gt;</span>/*<span class="nt">&lt;/url-pattern&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;/servlet-mapping&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/web-app&gt;</span></span></span></code></pre></div></div><p>The entry**/*** as a URL pattern causes all paths not occupied by the REST API (e.g.<strong>/shorten, /abc123</strong>) to be handled by the UI. The REST endpoints can still be accessed via explicit HttpServlet registrations with priority.</p><p>Alternatively, manual division via filters or explicit exclusion lists is possible, for example,<strong>/api/</strong>* should be strictly reserved for the REST branch.</p><h4 id="72-theme-integration-ohne-frontend-toolchain">7.2 Theme-Integration ohne Frontend-Toolchain</h4><p>A big advantage of Vaadin Flow in server mode is that there is<strong>no Node.js, npm or Webpack.</strong> All components and resources are handled at the JVM level and delivered as servlet resources at runtime. Themes can be defined directly in the classpath:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Theme</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"shortener-theme"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainLayout</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">AppLayout</span><span class="w"/><span class="p">{</span><span class="w"/><span class="p">...</span><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>In the directory frontend/themes/shortener-theme/, then lies:</p><ul><li>styles.css– own adjustments</li><li>theme.json– Metadata and component binding</li></ul><p>The Maven plugin vaadin-maven-plugin automatically generates the corresponding frontend bundle during package or install, fully integrated into the WAR build, without the need for external tools.</p><p>A typical Maven excerpt inshortener-war/pom.xml:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;plugin&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>com.vaadin<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>vaadin-maven-plugin<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;version&gt;</span>${vaadin.version}<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;executions&gt;</span></span></span><span class="line"><span class="cl">        <span class="nt">&lt;execution&gt;</span></span></span><span class="line"><span class="cl">            <span class="nt">&lt;goals&gt;</span></span></span><span class="line"><span class="cl">                <span class="nt">&lt;goal&gt;</span>prepare-frontend<span class="nt">&lt;/goal&gt;</span></span></span><span class="line"><span class="cl">                <span class="nt">&lt;goal&gt;</span>build-frontend<span class="nt">&lt;/goal&gt;</span></span></span><span class="line"><span class="cl">            <span class="nt">&lt;/goals&gt;</span></span></span><span class="line"><span class="cl">        <span class="nt">&lt;/execution&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;/executions&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/plugin&gt;</span></span></span></code></pre></div></div><p>The use of build-frontend. This is only required during initial deployment or when making theme changes. During operation, everything is loaded from the WAR.</p><h4 id="73-operation-in-tomcat-or-jetty">7.3 Operation in Tomcat or Jetty</h4><p>The resulting WAR file can be used in any<strong>Servlet 4.0+ compatible container</strong> for deployment. Recommended are:</p><ul><li><strong>Jetty 12</strong> for embedded deployments or local tests</li><li><strong>Apache Tomcat 10.1+</strong> for production-related scenarios</li><li><strong>Eclipse Servlet Container</strong> (e.g. Open Liberty) for modular applications</li></ul><p>Deployment is incredibly simple:</p><p><strong>cp target/shortener.war $CATALINA_BASE/webapps/</strong></p><p>After starting the container, the application can be accessed via the usual path:</p><p><strong>http://localhost:8080/shortener/</strong></p><p>REST endpoints (e.g., POST /shorten) and UI routes (/overview, /create) coexist. This results in a robust, clearly controlled deployment model:</p><ul><li><strong>No framework magic</strong></li><li><strong>No container lock-in
No build complexity</strong></li></ul><h3 id="8-conclusion-and-outlook">8. Conclusion and outlook</h3><p>With the integration of a Vaadin Flow-based interface, our URL shortener not only receives an interactive user interface, but also a complete, maintainable and production-ready management layer – realised<strong>exclusively with funds from the JDK.</strong> The graphical UI serves not as a decorative accessory, but as an integral part of the system architecture. It enables the secure creation, analysis, and maintenance of short links – typically via the same infrastructure used for REST endpoints or background processes.</p><p>The decision,<strong>exclusively Core Java and Vaadin Flow,</strong> has proven to be viable: The separation of module boundaries remains clear, deployment takes place as a classic WAR file, and all interactions – from validations and feedback logic to permission models – can be implemented in a comprehensible and transparent manner. Instead of framework magic, convention-over-configuration, or annotation-based autowiring, the focus here is once again on<strong>understandable system design</strong> down to the details.</p><p>Particularly noteworthy is the efficiency with which Vaadin Flow maps UI logic directly in the Java context. Developers retain control over state, access protection, and lifecycles – without relying on build toolchains, client routing, or third-party libraries. The associated simplification of the maintenance and security model is a significant advantage in the context of safety-critical infrastructure.</p><p>The system can also be further developed in the future without any architectural disruption. Possible next steps include:</p><ul><li><strong>Modularisation of the UI</strong> into reading and writing segments</li><li><strong>Asynchronous Event-Analysis</strong> via server push or WebSockets</li><li><strong>Connection to external security systems</strong> such as OAuth2 or LDAP</li><li><strong>Deployment on embedded Jetty</strong> for minimalist distributions</li><li><strong>Migration to native builds via GraalVM</strong> to reduce runtime overhead</li></ul><p>This makes the URL shortener suitable not only as a learning object or internal tool, but also as a blueprint for lean, robust and self-determined Java web applications – without vendor lock-in, without complex tooling dependencies and with the focus on<strong>Readability, transparency and control</strong>.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/08/ChatGPT-Image-15.-Aug.-2025-10_06_47.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/08/ChatGPT-Image-15.-Aug.-2025-10_06_47.png"/><enclosure url="https://svenruppert.com/images/2025/08/ChatGPT-Image-15.-Aug.-2025-10_06_47.png" type="image/jpeg" length="0"/></item><item><title>Connecting REST Services with Vaadin Flow in Core Java</title><link>https://svenruppert.com/posts/connecting-rest-services-with-vaadin-flow-in-core-java/</link><pubDate>Tue, 24 Jun 2025 09:39:25 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/connecting-rest-services-with-vaadin-flow-in-core-java/</guid><description>1. Introduction Why REST integration in Vaadin applications should not be an afterthought In modern web applications, communication with external services is no longer a special function, but an integral part of a service-oriented architecture. Even if Vaadin Flow, as a UI framework, relies on server-side Java logic to achieve a high degree of coherence between view and data models, the need to communicate with systems outside the application quickly arises. These can be simple public APIs—for example, for displaying weather data or currency conversions—as well as internal company services, such as license verification, user management, or connecting to a central ERP system.</description><content:encoded>&lt;![CDATA[<h2 id="1-introduction">1. Introduction</h2><h3 id="why-rest-integration-in-vaadin-applications-should-not-be-an-afterthought">Why REST integration in Vaadin applications should not be an afterthought</h3><p>In modern web applications, communication with external services is no longer a special function, but an integral part of a service-oriented architecture. Even if Vaadin Flow, as a UI framework, relies on server-side Java logic to achieve a high degree of coherence between view and data models, the need to communicate with systems outside the application quickly arises. These can be simple public APIs—for example, for displaying weather data or currency conversions—as well as internal company services, such as license verification, user management, or connecting to a central ERP system.</p><p>The challenge here lies not in technical feasibility. Still, in structural embedding, REST calls should not appear &ldquo;incidentally&rdquo; in the view code, but rather be abstracted and controlled in a cleanly encapsulated service layer. Especially in safety-critical or highly available systems, it&rsquo;s not just access that matters, but its failure behaviour, fallback strategies, and reusability.</p><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#1-introduction">1. Introduction</a><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#why-rest-integration-in-vaadin-applications-should-not-be-an-afterthought">Why REST integration in Vaadin applications should not be an afterthought</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#2-architecture-overview">2. Architecture overview</a><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#separation-of-ui-service-and-integration-best-practices-without-framework-ballast">Separation of UI, service and integration – best practices without framework ballast</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#3-http-client-in-java">3. HTTP-Client in Java</a><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#introduction-to-java-net-http-httpclient-as-a-modern-native-solution">Introduction to java.net.http.HttpClient as a modern, native solution</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#4a-der-rest-service-als-adapter">4a. Der REST-Service als Adapter</a><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#building-a-lean-java-class-for-rest-communication-with-object-mapping">Building a lean Java class for REST communication with object mapping</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#4b-der-rest-endpoint-in-core-java">4b. Der REST-Endpoint in Core Java</a><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#how-to-create-an-http-endpoint-without-frameworks-with-minimal-effort">How to create an HTTP endpoint without frameworks with minimal effort</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#minimal-example-rest-endpoint-that-returns-json">Minimal example: REST endpoint that returns JSON</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#what-is-happening-here-and-why-does-it-make-sense">What is happening here, and why does it make sense</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#outlook-modular-expansion-of-rest-servers">Outlook: Modular expansion of REST servers</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#5-type-safe-data-models-with-records">5. Type-safe data models with records</a><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#how-java-records-provide-clarity-and-security">How Java Records provide clarity and security</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#6-error-handling-and-robustness">6. Error handling and robustness</a><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#handling-status-codes-io-problems-and-termination-conditions">Handling status codes, IO problems and termination conditions</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#http-status-codes-as-a-starting-point">HTTP status codes as a starting point</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#dealing-with-io-errors">Dealing with IO errors</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#logging-with-thought">Logging with Thought</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#retry-and-timeout">Retry and Timeout</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#control-instead-of-surprise">Control instead of surprise</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#7-integration-in-die-vaadin-ui">7. Integration in die Vaadin-UI</a><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#example-use-in-the-view-layer-synchronous-and-understandable">Example use in the view layer – synchronous and understandable</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#ui-response-to-errors">UI response to errors</a><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#visible-feedback-without-blocking-interaction">Visible feedback without blocking interaction</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#asynchronous-extension-optional">Asynchronous extension (optional)</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#8-production-ready-safeguards">8. Production-ready safeguards</a><ol><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#authentication-time-limits-retry-logic-and-logging-what-you-should-pay-attention-to">Authentication, time limits, retry logic and logging – what you should pay attention to</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#8-1-authentication-headers-instead-of-framework-magic">8.1 Authentication: Headers instead of framework magic</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#8-2-time-limits-protection-against-hanging-services">8.2 Time limits: Protection against hanging services</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#8-3-retry-strategies-controlled-and-limited">8.3 Retry strategies: controlled and limited</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#8-4-logging-and-correlation">8.4 Logging and Correlation</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#8-5-resilience-through-convention">8.5 Resilience through convention</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#9-asynchronous-extension-with-completablefuture">9. Asynchronous extension with CompletableFuture</a>
1.<a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#when-and-how-to-integrate-non-blocking-http-calls-into-ui-workflows">When and how to integrate non-blocking HTTP calls into UI workflows</a>
2.<a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#starting-point-blocking-rest-calls">Starting point: Blocking REST calls</a>
3.<a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#solution-non-blocking-via-completablefuture">Solution: Non-blocking via CompletableFuture</a>
4.<a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#error-handling-in-the-futures-chain">Error handling in the futures chain</a>
5.<a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#progress-indicator-and-ui-states">Progress indicator and UI states</a>
6.<a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#combination-with-multiple-requests">Combination with multiple requests</a></li><li><a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#10-conclusion-and-outlook">10. Conclusion and outlook</a>
1.<a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#rest-adapters-as-a-stable-bridge-in-service-oriented-architectures">REST adapters as a stable bridge in service-oriented architectures</a>
2.<a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#outlook-rest-in-modular-and-service-oriented-vaadin-applications">Outlook: REST in modular and service-oriented Vaadin applications</a>
3.<a href="https://svenruppert.com/2025/06/24/connecting-rest-services-with-vaadin-flow-in-core-java/#what-remains">What remains?</a></li></ol><p>Vaadin Flow offers no built-in mechanism for this, and that&rsquo;s a good thing. By deliberately avoiding magical abstractions or framework coupling (such as Spring RestTemplate or Feign Clients), it allows maximum control over REST integration. With Java 24 and the now mature<strong>java.net.http.HttpClient,</strong> all required building blocks are available directly in the Core JDK – performant, maintainable and framework-independent.</p><p>This chapter, therefore, marks the beginning of a series of articles that demonstrate how to connect a Vaadin Flow application to an external REST endpoint, without external dependencies, but with a clear focus on readability, architecture, and security. The goal is not only to write functioning HTTP calls, but also to understand REST as part of a clean, modular software architecture.</p><h2 id="2-architecture-overview">2. Architecture overview</h2><h3 id="separation-of-ui-service-and-integration--best-practices-without-framework-ballast">Separation of UI, service and integration – best practices without framework ballast</h3><p>A clear architecture is the foundation of any maintainable application, regardless of whether it is monolithic, modular, or service-oriented. This is especially true for Vaadin Flow, as UI components are defined server-side in Java, thus eliminating any rigid boundaries between the underlying layers. This proximity between the user interface and backend logic is one of Vaadin&rsquo;s most significant advantages, but it also presents an architectural challenge when integrating external systems.</p><p>The integration of a REST endpoint should therefore always be done via an intermediary service layer. This refers to a dedicated &ldquo;adapter&rdquo; that, on the one hand, encapsulates the connection to the external REST service and, on the other hand, provides a type-safe, stable API for the UI layer. The advantage is obvious: Changes to the format of the remote system or the protocol behaviour do not affect the application structure or the user interface—they remain isolated in the adapter module.</p><p>This separation can be elegantly implemented in a Vaadin project without additional frameworks. A standard layered structure consists of the following components:</p><ul><li><strong>UI layer:</strong> It contains the Vaadin views and components that work exclusively with Java data objects. No network communication or JSON processing takes place here.</li><li><strong>Service shift:</strong> It mediates between the UI and integration, potentially coordinates multiple adapters, and is responsible for domain-specific business logic. This layer is unaware of the technical details of the REST protocol.</li><li><strong>Adapter layer:</strong> It provides the actual REST access. This includes HTTP calls, header management, timeout handling, JSON deserialization, and error evaluation. This layer has no UI classes or interactive logic.</li></ul><p>This modular, three-part structure enables a testable and extensible framework. It enables the targeted simulation of REST access in unit tests or the use of alternative implementations (e.g., for offline modes or mocking in the development system) – all without requiring refactoring of the UI code.</p><p>Especially in Java 24, this principle can be excellently complemented with records, sealed types, and functional interfaces, making the resulting architecture not only stable but also concise and modern. The following figure (not included) illustrates how the data flows between the layers, from the UI request to the service, to the adapter, and back – always clearly separated but tightly interlinked by typed interfaces.</p><p>With this foundation in mind, we will discuss the technical implementation of REST communication in the next chapter, specifically using the<strong>HttpClient</strong> , which has been part of the JDK since Java 11 and has now established itself as a high-performance, well-tested standard.</p><h2 id="3-http-client-in-java">3. HTTP-Client in Java</h2><h3 id="introduction-to-javanethttphttpclient-as-a-modern-native-solution">Introduction to<strong>java.net.http.HttpClient</strong> as a modern, native solution</h3><p>Since Java 11, the java.net.http.HttpClient, a powerful, standards-compliant, and thread-safe HTTP client, is available and fully included in the JDK. In Java, this client has long been established as a reliable means of REST communication, especially in projects that deliberately avoid frameworks to achieve maximum control, portability, and predictability.</p><p>In contrast to previous solutions, such as<strong>HttpURLConnection</strong> , which were laborious and error-prone, the modern<strong>HttpClient</strong> features a straightforward, fluent-based API that supports both synchronous and asynchronous communication using<strong>CompletableFuture.</strong> It is resource-efficient, supports automatic redirects, HTTP/2, and can be equipped with configurable timeouts, proxies, and authentication.</p><p>In practice, the use of the HttpClient begins with its configuration as a reusable instance:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">HttpClient</span><span class="w"/><span class="n">client</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">HttpClient</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="na">connectTimeout</span><span class="p">(</span><span class="n">Duration</span><span class="p">.</span><span class="na">ofSeconds</span><span class="p">(</span><span class="n">5</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="na">version</span><span class="p">(</span><span class="n">HttpClient</span><span class="p">.</span><span class="na">Version</span><span class="p">.</span><span class="na">HTTP_2</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="na">build</span><span class="p">();</span></span></span></code></pre></div></div><p>The HttpClient is immutable and thread-safe. Once created, it can be used for any number of requests. This is especially crucial in server applications, as repeated regeneration would not only be inefficient but also potentially problematic for resource utilisation.</p><p>For the specific request, a<strong>HttpRequest</strong> that encapsulates all parameters such as URI, HTTP method, headers and optional body data:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">HttpRequest</span><span class="w"/><span class="n">request</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">HttpRequest</span><span class="p">.</span><span class="n">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">uri</span><span class="p">(</span><span class="n">URI</span><span class="p">.</span><span class="k">create</span><span class="p">(</span><span class="s2">"https://api.example.com/data"</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">header</span><span class="p">(</span><span class="s2">"Accept"</span><span class="p">,</span><span class="w"/><span class="s2">"application/json"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="k">GET</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="n">build</span><span class="p">();</span></span></span></code></pre></div></div><p>The execution is synchronous with:</p><p><strong>HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());</strong></p><p>Alternatively, the method<strong>sendAsync()</strong> can be used to realise non-blocking interactions based on CompletableFuture – a model that can be particularly useful for UI-oriented or reactive applications when blocking HTTP processing should be avoided.</p><p>What makes this API particularly special is the strict separation of request construction, client configuration, and result handling—no hidden magic, reflection, or XML-based configuration is used. Control lies entirely with the developer, and the API is understandable and type-safe.</p><p>The return is always made via a<strong>HttpResponse<T/>, where the type T is determined by the selected BodyHandler—typically String, byte[], InputStream, or Void. This separation allows for efficient processing of both simple text responses and binary data.</p><p>In the next chapter, I&rsquo;ll show how this HTTP infrastructure can be converted into a production-ready adapter class, including header handling, error checking, and JSON deserialization into Java records. The goal is not just functional communication, but maintainable, robust, and testable code.</p><h2 id="4a-der-rest-service-als-adapter">4a. Der REST-Service als Adapter</h2><h3 id="building-a-lean-java-class-for-rest-communication-with-object-mapping">Building a lean Java class for REST communication with object mapping</h3><p>After the technical basics of the<strong>HttpClient</strong> are established in Java 24, the central question arises: How can REST calls be encapsulated so that they remain reusable, extensible, and UI-independent? The answer lies in the adapter principle, explicitly implemented as a standalone service class that encapsulates access to a specific REST endpoint and provides a type-safe API for the application core.</p><p>Such an adapter is responsible for:</p><ul><li>Structure and execution of the HTTP request,</li><li>Interpretation of the HTTP response code,</li><li>Transformation of the response data (e.g. JSON) into a Java model,</li><li>optional: logging, header management, error handling and retry logic.</li></ul><p>However, these tasks shouldn&rsquo;t be performed in the UI layer or presenter classes. Instead, a final-declared Java class is recommended, whose methods are specifically designed to load or send specific domain-specific data objects – for example:<strong>fetchUserData()</strong> ,<strong>submitOrder()</strong> ,<strong>validateLicenseKey()</strong>.</p><p>A minimal adapter for a GET call with a JSON response could look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.io.IOException;</span></span><span class="line"><span class="cl">import java.net.URI;</span></span><span class="line"><span class="cl">import java.net.http.*;</span></span><span class="line"><span class="cl">import java.time.Duration;</span></span><span class="line"><span class="cl">import com.fasterxml.jackson.databind.ObjectMapper;</span></span><span class="line"><span class="cl">import com.example.model.DataObject;</span></span><span class="line"><span class="cl">public final class ExternalApiService {</span></span><span class="line"><span class="cl">    private final HttpClient client;</span></span><span class="line"><span class="cl">    private final ObjectMapper mapper;</span></span><span class="line"><span class="cl">    private final URI endpoint;</span></span><span class="line"><span class="cl">    public ExternalApiService(String baseUrl) {</span></span><span class="line"><span class="cl">        this.client = HttpClient.newBuilder()</span></span><span class="line"><span class="cl">                .connectTimeout(Duration.ofSeconds(5))</span></span><span class="line"><span class="cl">                .build();</span></span><span class="line"><span class="cl">        this.mapper = new ObjectMapper();</span></span><span class="line"><span class="cl">        this.endpoint = URI.create(baseUrl + "/data");</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">    public DataObject fetchData() throws IOException, InterruptedException {</span></span><span class="line"><span class="cl">        HttpRequest request = HttpRequest.newBuilder()</span></span><span class="line"><span class="cl">                .uri(endpoint)</span></span><span class="line"><span class="cl">                .header("Accept", "application/json")</span></span><span class="line"><span class="cl">                .GET()</span></span><span class="line"><span class="cl">                .build();</span></span><span class="line"><span class="cl">        HttpResponse<span class="nt">&lt;String&gt;</span> response = client.send(request, HttpResponse.BodyHandlers.ofString());</span></span><span class="line"><span class="cl">        if (response.statusCode() != 200) {</span></span><span class="line"><span class="cl">throw new IOException("Unexpected status code: " + response.statusCode());</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">        return mapper.readValue(response.body(), DataObject.class);</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The aim of this class is<strong>not</strong> to be flexible for any endpoint or generic API, but rather to be<strong>concrete and targeted</strong> to serve exactly one external use case. This clarity is an advantage, not a disadvantage: It facilitates refactoring, testability, and secure extension if, for example, new headers, authentication mechanisms, or error codes are introduced.</p><p>The class encapsulates the entire technical aspect of HTTP communication. It is entirely independent of the UI and has no views, components, or user interactions. It can therefore be easily integrated into JUnit tests—either directly or via interface derivation for mocking.</p><p>The record class used<strong>DataObject</strong> serves as a transfer object that is automatically deserialised from JSON by the ObjectMapper:</p><p><strong>public record DataObject(String id, String value) {}</strong></p><p>This combination of a concise object structure and a stable communication layer forms the backbone of a clean REST integration—without frameworks, without magic, but with a clear separation of responsibilities. Errors can be specifically intercepted, additional parameters can be easily incorporated, and the architecture remains transparent.</p><h2 id="4b-der-rest-endpoint-in-core-java">4b. Der REST-Endpoint in Core Java</h2><h3 id="how-to-create-an-http-endpoint-without-frameworks-with-minimal-effort">How to create an HTTP endpoint without frameworks with minimal effort</h3><p>A REST endpoint is essentially nothing more than a specialised HTTP handler. While Spring or JakartaEE use controllers, annotations, and automatic serialisation, a pure Java approach requires a bit more manual work, but is rewarded with complete control over behaviour, performance, security boundaries, and dependencies.</p><p>Since Java 18, the<strong>com.sun.net.httpserver.HttpServer</strong> API, a lightweight HTTP server, has been available. It is ideal for simple REST endpoints—for example, as a test mock, an internal microservice, or in this case, as a local data source for Vaadin. In combination with a JSON mapping (e.g.ObjectMapper from Jackson), it creates a REST backend without any external platform.</p><h3 id="minimal-example-rest-endpoint-that-returns-json">Minimal example: REST endpoint that returns JSON</h3><p>The following class starts an HTTP server on port 8080, which, in a GET on<strong>/data,</strong> returns a JSON object:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.sun.net.httpserver.HttpServer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.sun.net.httpserver.HttpHandler</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.sun.net.httpserver.HttpExchange</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.fasterxml.jackson.databind.ObjectMapper</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.OutputStream</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.InetSocketAddress</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SimpleRestServer</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">HttpServer</span><span class="w"/><span class="n">server</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">HttpServer</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">InetSocketAddress</span><span class="p">(</span><span class="n">8080</span><span class="p">),</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">server</span><span class="p">.</span><span class="na">createContext</span><span class="p">(</span><span class="s">"/data"</span><span class="p">,</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DataHandler</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">server</span><span class="p">.</span><span class="na">setExecutor</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w"/><span class="c1">// default executor</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">server</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Server is running on http://localhost:8080/data"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">static</span><span class="w"/><span class="kd">class</span><span class="nc">DataHandler</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HttpHandler</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">ObjectMapper</span><span class="w"/><span class="n">mapper</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectMapper</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handle</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">exchange</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">"GET"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestMethod</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">405</span><span class="p">,</span><span class="w"/><span class="o">-</span><span class="n">1</span><span class="p">);</span><span class="w"/><span class="c1">// Method Not Allowed</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="kd">var</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DataObject</span><span class="p">(</span><span class="s">"abc123"</span><span class="p">,</span><span class="w"/><span class="s">"42.0"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"/><span class="n">response</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">mapper</span><span class="p">.</span><span class="na">writeValueAsString</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseHeaders</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span><span class="w"/><span class="s">"application/json"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">200</span><span class="p">,</span><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">getBytes</span><span class="p">().</span><span class="na">length</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseBody</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="na">getBytes</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">record</span><span class="nc">DataObject</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">id</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">value</span><span class="p">)</span><span class="w"/><span class="p">{}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="what-is-happening-here-and-why-does-it-make-sense">What is happening here, and why does it make sense</h3><ul><li>The server listens on port 8080 and accepts requests/data in contrast to.</li><li>Only GET requests are accepted; all other HTTP methods receive a status code of 405.</li><li>The result object is an ObjectMapper-serialised instance – this is the same data type (DataObject) that is also used in the REST adapter on the client side.</li><li>The answer will be delivered with the appropriate Content-Type, specifically application/json.</li></ul><p>This implementation is minimal but functional. It can be tested immediately, run locally, and queried from the Vaadin UI via the adapter described in Chapter 4a. It requires no XML, no DI container, no JAR hierarchies—just plain Java.</p><h3 id="outlook-modular-expansion-of-rest-servers">Outlook: Modular expansion of REST servers</h3><p>A REST server based on this principle can be easily expanded:</p><ul><li>More createContext(&hellip;)-Handler for additional endpoints</li><li>Support for POST, PUT, DELETE with Payload-Parsing</li><li>Path parameters through simple URI parsing</li><li>Authentication logic via header evaluation</li><li>Logging, metrics and request correlation as an extension</li><li>Outsourcing to modules if multiple services are to be created</li></ul><p>This setup can be used particularly elegantly in development and testing, for example, to simulate external services or to reproduce specific error cases (404, 500).</p><h2 id="5-type-safe-data-models-with-records">5. Type-safe data models with records</h2><h3 id="how-java-records-provide-clarity-and-security">How Java Records provide clarity and security</h3><p>The data model plays a central role in the communication between a Vaadin Flow application and an external REST service. It bridges the gap between the JSON representation of the remote resource and the object representation processed in Java. The clearer, more type-safe, and immutable this model is, the more robust the application architecture becomes, especially when the REST interface changes or when used in parallel UI contexts.</p><p>Since Java 16, so-called<em>Records</em> – a language feature explicitly designed for this type of data structure: compact, immutable objects with semantically unambiguous data transport characteristics. A record is not a replacement for a complete domain entity with behaviour, but the ideal representation for<strong>structured response data</strong> from REST endpoints.</p><p>An example: A REST service delivers JSON responses like</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">json</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span></span></span><span class="line"><span class="cl">  <span class="nt">"id"</span><span class="p">:</span><span class="s2">"abc123"</span><span class="p">,</span></span></span><span class="line"><span class="cl">  <span class="nt">"value"</span><span class="p">:</span><span class="s2">"42.0"</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>A suitable Java record to represent this structure looks like this:</p><p><strong>public record DataObject(String id, String value) {}</strong></p><p>This record is:</p><ul><li><strong>Unchangeable</strong> : Its fields are final and publicly readable, but not modifiable.</li><li><strong>Comparable</strong> : equals() and hashCode() are automatically implemented correctly.</li><li><strong>Structured</strong> : The signature also serves as documentation of the expected JSON format.</li><li><strong>Compact</strong> : Compared to classic POJOs, there is no boilerplate code.</li></ul><p>Deserialization from JSON with an<strong>ObjectMapper</strong>(as shown in Chapter 4) works directly and without further annotations as long as the field names match. This promotes readability and reduces the risk of hidden deserialization errors.</p><p>Additionally, records are well-suited for use as<strong>DTOs (Data Transfer Objects).</strong> For example, when multiple external REST endpoints deliver different aspects of the same domain concept, each with its own response structure. Strict type safety of records then protects against unintentional mixing or incorrect field usage.</p><p>Records can also be nested to represent more complex JSON structures—for example, when an object contains a list of other objects or provides structured metadata. Here, too, developers benefit from the expressiveness, readability, and stability that records offer over traditional getter and setter classes.</p><p>In the context of a Vaadin application, this has an immediate positive effect: Records can be used directly in UI components, for example, for display in<strong>Grid</strong> ,<strong>FormLayout</strong> or custom components. The data objects themselves contain no presentation logic, but remain purely structural—and this is precisely what is desirable in a separated architecture.</p><p>In the next chapter, we will demonstrate how this structured REST communication can be utilised in the UI layer and identify patterns that have proven successful in practice for effectively linking error handling, response logic, and user interaction.</p><h2 id="6-error-handling-and-robustness">6. Error handling and robustness</h2><h3 id="handling-status-codes-io-problems-and-termination-conditions">Handling status codes, IO problems and termination conditions</h3><p>In practice, a REST call is more than just an HTTP request. It is an insecure operation over a potentially unstable medium, with numerous possible sources of error, including network problems, timeout behaviour, invalid response formats, rejected requests, or temporary server errors. The quality of a REST integration is therefore evident,<strong>not in successful communication</strong> , but instead in its behaviour in the event of a mistake.</p><p>Especially in Vaadin Flow applications that remain active on the server side for extended periods, a single failed REST call can result in a view not being initialised correctly, a user action being suspended, or incomplete data being displayed. Therefore, the goal is to implement error handling that:</p><ul><li>semantically distinguishes between transport-related and technical errors,</li><li>provides defined return values ​​or exceptions to which the UI can react specifically,</li><li>and established a comprehensible logging strategy.</li></ul><h3 id="http-status-codes-as-a-starting-point">HTTP status codes as a starting point</h3><p>Even the interpretation of the response code requires care. While<strong>200 OK</strong> and<strong>201 Created</strong> usually indicate success, other codes such as<strong>204 No Content</strong> ,<strong>404 Not Found,</strong> or<strong>409 Conflict</strong> can be entirely intentional – and should not be treated as errors across the board. The business context is crucial: A &ldquo;not found&rdquo; error can be deliberate and trigger a specific UI response, such as an empty display or an alternative form.</p><p>The adapter should therefore not perform generic error handling across all code, but rather explicitly evaluate what makes sense in the respective application scenario. Example:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="na">statusCode</span><span class="p">()</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">404</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">return</span><span class="w"/><span class="n">Optional</span><span class="p">.</span><span class="na">empty</span><span class="p">();</span><span class="w"/><span class="c1">// targeted reaction to missing resource</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="na">statusCode</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="n">200</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Unexpected status: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">statusCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="dealing-with-io-errors">Dealing with IO errors</h3><p>Transport errors – such as timeouts, DNS problems, or connection failures – typically trigger<strong>IOException</strong> or<strong>InterruptedException.</strong> These should not be swallowed in the adapter or<strong>RuntimeException.</strong> Instead, the caller is signalled that the response is not usable. An error message can then be displayed in the UI without the application entering an undefined state.</p><h3 id="logging-with-thought">Logging with Thought</h3><p>Errors that cannot be explained by user interaction should be logged on the server side, but not necessarily displayed to the user. A short<strong>Logger.warn(&hellip;)</strong> An entry can help here, for example, to make external API errors traceable without flooding log files with irrelevant details. It is recommended to use different log levels depending on the error type: INFO for normal non-detectable behaviour, WARN for inconsistent responses, and ERROR only for real failures or bugs.</p><h3 id="retry-and-timeout">Retry and Timeout</h3><p>In production systems, it can be useful to automatically retry certain errors (e.g., 503 Service Unavailable or IOException) with backoff and limitation. However, this logic should not be implemented in the UI code, but rather in the adapter or a delegated &ldquo;RetryHandler.&rdquo; Timeouts should also be set explicitly:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">HttpRequest</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="na">timeout</span><span class="p">(</span><span class="n">Duration</span><span class="p">.</span><span class="na">ofSeconds</span><span class="p">(</span><span class="n">3</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="na">build</span><span class="p">();</span></span></span></code></pre></div></div><p>Without defined time limits, a REST call can inadvertently lead to a blocking UI thread, especially when called synchronously.</p><h3 id="control-instead-of-surprise">Control instead of surprise</h3><p>The central goal of any error handling is to ensure the<strong>Predictability of</strong><strong>behaviour</strong> : A REST adapter should always provide defined semantics—either a correct result object, a declared exception, or an optional error that is explicitly checked. No hidden null values, no logic in catch blocks, no silent terminations.</p><p>The resulting UI remains able to react in a controlled manner, whether by displaying notifications, activating a retry button, or switching to offline data. The result is not only a more robust architecture but also a better user experience.</p><p>In the next chapter, I will show how these REST results are integrated into the UI layer of a Vaadin application, including concrete examples of loading operations, state changes, and error messages in the user context.</p><h2 id="7-integration-in-die-vaadin-ui">7. Integration in die Vaadin-UI</h2><h3 id="example-use-in-the-view-layer--synchronous-and-understandable">Example use in the view layer – synchronous and understandable</h3><p>The clean separation between REST communication and UI logic is a central principle of maintainable software architecture. However, there must come a point where external data actually ends up in the user interface—be it in a table, a form, or as part of an interactive visualisation. The integration of the REST adapter into the Vaadin UI is ideally done in this case.<strong>synchronous, type-confident and consciously visible</strong> in the code – instead of hidden “magic” or automatic binding.</p><p>At the centre is a view, e.g., a class that is<strong>a VerticalLayout</strong> or<strong>Composite<Div/> and inherits. The REST adapter can be called within this view, typically when constructing the view or during a targeted user interaction. The following example demonstrates a simple use case: When loading the view, a dataset should be retrieved from an external REST service and displayed.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">DataView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">ExternalApiService</span><span class="w"/><span class="n">apiService</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">DataView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">apiService</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ExternalApiService</span><span class="p">(</span><span class="s">"https://api.example.com"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">DataObject</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">apiService</span><span class="p">.</span><span class="na">fetchData</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">showData</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">showError</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">showData</span><span class="p">(</span><span class="n">DataObject</span><span class="w"/><span class="n">data</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="s">"ID: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">data</span><span class="p">.</span><span class="na">id</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Span</span><span class="p">(</span><span class="s">"Wert: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">data</span><span class="p">.</span><span class="na">value</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">showError</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">message</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Error loading data: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">message</span><span class="p">,</span><span class="w"/><span class="n">5000</span><span class="p">,</span><span class="w"/><span class="n">Notification</span><span class="p">.</span><span class="na">Position</span><span class="p">.</span><span class="na">MIDDLE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This type of integration is deliberately kept simple, but it demonstrates a central principle: The REST adapter is an<em>explicit component</em> of view initialisation. There&rsquo;s no hidden binding, no automatic magic, just traceable, step-by-step data flow. This not only facilitates debugging but also makes state transitions visible and controllable.</p><h3 id="ui-response-to-errors">UI response to errors</h3><h4 id="visible-feedback-without-blocking-interaction">Visible feedback without blocking interaction</h4><p>The integration of an external REST endpoint into a Vaadin Flow application must not only be technically reliable, but it must also, and most importantly, behave predictably and stably within the user interface. In production scenarios, it is perfectly normal for REST services to be temporarily unavailable, provide slow responses, or fail with an error status, such as<strong>500 Internal Server Error, 403 Forbidden,</strong> or<strong>429 Too Many Requests</strong>. What matters is not the error itself, but how it affects the user experience.</p><p>In the example shown, a non-blocking notification is used in the event of an error. It appears in the centre of the screen and briefly informs the user that the data loading failed. This approach is recommended for several reasons:</p><ol><li><p><strong>The user remains able to act</strong> :<br>
A notification doesn&rsquo;t interrupt the interaction flow. No dialogue opens, no loading process freezes, and no navigation is blocked. The application remains fully usable, which is especially beneficial in multi-part views or for later, optional loading operations.</p></li><li><p>**The error message is visible in context:
**Instead of hiding technical details in logs or simply not displaying &ldquo;something,&rdquo; the user is actively informed about the error—transparently, but without technical depth. The information isn&rsquo;t intended for analysis, but rather to correct expectations:<em>“Something should have appeared here, but is currently unavailable.”</em> _<br>
_</p></li><li><p>**The application does not exit into an undefined state:
**There&rsquo;s no exception chaining into the UI thread, no empty interface without explanation, and no sudden switching to another route. The user experience remains consistent, ​​even in the event of an error.</p></li></ol><p>For more sophisticated scenarios, further reaction patterns are also available:</p><ul><li><p>**Retry button or “Try again” action:
**A button can be displayed directly below the error message to re-execute the REST call. This leaves repeatability up to the user—useful, for example, in cases of temporary network errors or rate-limited APIs.</p></li><li><p>**Fallback display or partial information:
**If previous data (e.g., from local storage or a previous session) is available, it can be displayed as a placeholder, along with a note that the current data cannot be loaded at the moment.</p></li><li><p>**Visual placeholders or skeleton layouts:
**Instead of just displaying empty components, a visual framework can be displayed – for example, grey bars that indicate the expected content but symbolise it:<em>“These data are not yet available.”</em> _<br>
_</p></li><li><p>**Consistent error design across the entire application:
**It&rsquo;s recommended to establish a central component or utility class for displaying errors that&rsquo;s used system-wide. This ensures consistent display regardless of whether a REST error occurs in the dashboard, a dialogue, or a detailed view.</p></li></ul><p>In summary, this approach pursues one central goal:<strong>Errors may occur, but they should not take over the control flow.</strong> The UI remains in the hands of the user, not the infrastructure. This creates a resilient interface that looks professional and inspires trust, even when systems in the background aren&rsquo;t working perfectly.</p><h3 id="asynchronous-extension-optional">Asynchronous extension (optional)</h3><p>In some cases, asynchronous integration can be useful – for example, when dealing with long loading times or interactions that must not block. This can also be achieved with Vaadin and the<strong>HttpClient.</strong> A typical strategy is to load the data in a background thread and then use<strong>UI.access(&hellip;)</strong> back to the UI thread:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">UI</span><span class="w"/><span class="n">ui</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">CompletableFuture</span><span class="p">.</span><span class="na">supplyAsync</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="n">apiService</span><span class="p">.</span><span class="na">fetchData</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">CompletionException</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}).</span><span class="na">thenAccept</span><span class="p">(</span><span class="n">data</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">showData</span><span class="p">(</span><span class="n">data</span><span class="p">)))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">.</span><span class="na">exceptionally</span><span class="p">(</span><span class="n">ex</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">showError</span><span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="na">getCause</span><span class="p">().</span><span class="na">getMessage</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">});</span></span></span></code></pre></div></div><p>This variant is not necessary, but it is helpful in scenarios with many concurrent REST requests or slow-responding APIs.</p><p>Integrating a REST adapter into a Vaadin flow view is best done in a structured, explicit, and UI-specific manner. The view is not responsible for constructing the request or interpreting the HTTP code—it only consumes the Java objects provided by the adapter. Errors are handled, states are modelled consciously, and the user interface remains responsive and understandable.</p><p>In the next chapter, we&rsquo;ll take a look at production-ready extensions: authentication, header handling, retry strategies, and logging—everything that makes REST access robust and secure.</p><h2 id="8-production-ready-safeguards">8. Production-ready safeguards</h2><h3 id="authentication-time-limits-retry-logic-and-logging--what-you-should-pay-attention-to">Authentication, time limits, retry logic and logging – what you should pay attention to</h3><p>A REST adapter that works reliably in development and test environments is far from production-ready. As soon as a Vaadin Flow application is embedded in real infrastructures—with access to external APIs, network latencies, security policies, and operational requirements—additional protection mechanisms must be established. These not only address error cases, but also, and most importantly,<strong>Non-fault cases under</strong><strong>challenging conditions,</strong> such as authentication, latency, rate limiting, logging requirements, or the protection of sensitive data.</p><p>The following discussion demonstrates how production-ready REST adapters can be developed using the resources of the Java Development Kit (JDK).</p><h3 id="81-authentication-headers-instead-of-framework-magic">8.1 Authentication: Headers instead of framework magic</h3><p>External APIs typically require authentication, which can be in the form of a static token, a Basic Auth combination, or an OAuth 2.0 bearer token. In production-based scenarios, multiple variants are often used simultaneously – for example, an API that expects separate public and admin keys.</p><p>Instead of shifting this logic to the HTTP client construction, it is recommended to provide your helper methods in the adapter, for example:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">HttpRequest</span><span class="p">.</span><span class="na">Builder</span><span class="w"/><span class="nf">authenticatedRequest</span><span class="p">(</span><span class="n">URI</span><span class="w"/><span class="n">uri</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"/><span class="n">HttpRequest</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="na">you</span><span class="p">(</span><span class="n">s</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="na">header</span><span class="p">(</span><span class="s">"Authorization"</span><span class="p">,</span><span class="w"/><span class="s">"Bearer "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">tokenProvider</span><span class="p">.</span><span class="na">getCurrentToken</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This not only isolates the technical mechanism (header), but also abstracts the source of supply (e.g., token rotation, expiration time). The token itself can be renewed regularly, read from a secrets store, or dynamically calculated, without affecting the call point in the UI code.</p><h3 id="82-time-limits-protection-against-hanging-services">8.2 Time limits: Protection against hanging services</h3><p>Without explicitly set time limits, the<strong>HttpClient</strong> theoretically has unlimited waiting time for a response, which can be fatal in server-side applications. To control latency and protect UI threads, timeouts should be set both in the client and per request:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">HttpClient</span><span class="w"/><span class="n">client</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">HttpClient</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="na">connectTimeout</span><span class="p">(</span><span class="n">Duration</span><span class="p">.</span><span class="na">ofSeconds</span><span class="p">(</span><span class="n">5</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">HttpRequest</span><span class="w"/><span class="n">request</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">HttpRequest</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="na">timeout</span><span class="p">(</span><span class="n">Duration</span><span class="p">.</span><span class="na">ofSeconds</span><span class="p">(</span><span class="n">3</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="na">type</span><span class="p">(...)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="na">GET</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">.</span><span class="na">build</span><span class="p">();</span></span></span></code></pre></div></div><p>This separation allows global parameters to be differentiated from the behaviour of individual calls – e.g., for particularly sensitive endpoints or third-party interfaces with historically fluctuating response times.</p><h3 id="83-retry-strategies-controlled-and-limited">8.3 Retry strategies: controlled and limited</h3><p>Not all errors mean that a request fails permanently. Temporary DNS problems can often be intercepted by targeted retries**,** which can resolve<strong>503 Service Unavailable</strong> errors or connectionless gateways – provided the retries are limited, staggered in time, and context-dependent.</p><p>An example of a<strong>simple</strong> Retry loop without an external library:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">DataObject</span><span class="w"/><span class="nf">fetchDataWithRetry</span><span class="p">()</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">int</span><span class="w"/><span class="n">attempts</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">3</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;=</span><span class="w"/><span class="n">attempts</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">return</span><span class="w"/><span class="n">fetchData</span><span class="p">();</span><span class="w"/><span class="c1">// normal method with HttpClient</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">i</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">attempts</span><span class="p">)</span><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Maximum attempts reached"</span><span class="p">,</span><span class="w"/><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">i</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">500L</span><span class="p">);</span><span class="w"/><span class="c1">// linear backoff</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">InterruptedException</span><span class="w"/><span class="n">ie</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">interrupt</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Retry aborted"</span><span class="p">,</span><span class="w"/><span class="n">ie</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalStateException</span><span class="p">(</span><span class="s">"Unreachable code"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Retry logic must never grow uncontrollably, so that it performs logging and that it is only active in the case of explicitly temporary errors, not in the case of<strong>401</strong> ,<strong>403</strong> or<strong>404</strong>.</p><h3 id="84-logging-and-correlation">8.4 Logging and Correlation</h3><p>In production-ready systems, REST calls are a relevant component of auditing, error analysis, and performance monitoring. Therefore, each call should be systematically recorded in the log, but in a differentiated manner:</p><ul><li><strong>DEBUG</strong> : technical details such as URIs, headers, and response sizes</li><li><strong>INFO</strong> : regular REST call with semantic meaning (e.g. license check, status change)</li><li><strong>WARN</strong> : temporary errors or unexpected responses</li><li><strong>ERROR</strong> : permanent errors, incomplete response data, uncaught exceptions</li></ul><p>Additionally, a correlation token should be included with each request, for example, as a UUID in the<strong>X-Correlation-ID-Header</strong> , allowing REST calls to be traced across multiple systems. This can also be encapsulated centrally in the adapter:</p><p><strong>private HttpRequest.Builder withCorrelation(HttpRequest.Builder builder) {</strong></p><p>** return builder.header(&ldquo;X-Correlation-ID&rdquo;, UUID.randomUUID().toString());**</p><p><strong>}</strong></p><h3 id="85-resilience-through-convention">8.5 Resilience through convention</h3><p>A stable REST adapter is not “intelligent” in the sense of being dynamic, but<strong>somewhat predictable, complete, and conservative.</strong> Every response is either a result or an exception—never a silence. The data formats are stable and defensively parsed. If expectations are not met, a defined termination occurs. This protects both the UI logic and the user experience, forming the basis for maintainable systems with precise error semantics.</p><h2 id="9-asynchronous-extension-with-completablefuture">9. Asynchronous extension with CompletableFuture</h2><h3 id="when-and-how-to-integrate-non-blocking-http-calls-into-ui-workflows">When and how to integrate non-blocking HTTP calls into UI workflows</h3><p>In Vaadin Flow applications, the code is traditionally structured synchronously – views respond to user actions, load data, and display results. However, as soon as REST calls come into play, which can take longer than a few milliseconds, the question inevitably arises:<em>How can I offload HTTP communication without blocking the UI thread – and without having to resort to a reactive framework?</em></p><p>The answer to this is provided by the JDK integrated class<strong>CompletableFuture.</strong> It allows you to declaratively model asynchronous workflows, precisely control concurrency, and return the result to the UI in a controlled manner upon success or failure. In conjunction with Vaadin&rsquo;s server-side UI model, only one measure is crucial: access to UI components must occur in the correct thread context.</p><h3 id="starting-point-blocking-rest-calls">Starting point: Blocking REST calls</h3><p>In a synchronous example, the call typically looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">DataObject</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">apiService</span><span class="p">.</span><span class="na">fetchData</span><span class="p">();</span><span class="w"/><span class="c1">// synchronous call</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">showData</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w"/><span class="c1">// direct display in the UI</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">showError</span><span class="p">(</span><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>As long as response times are low and the view is already being constructed synchronously, this is entirely unproblematic. It becomes critical when REST calls follow user actions—for example, after clicking &ldquo;Refresh&rdquo; or during filter operations on large data sets. Here, a blocking call would lead to a noticeable delay in the UI.</p><h3 id="solution-non-blocking-via-completablefuture">Solution: Non-blocking via CompletableFuture</h3><p>To offload the REST call without blocking the UI, the access can be moved to a separate background thread, and the result can be safely returned to the UI later:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">UI</span><span class="w"/><span class="n">ui</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">UI</span><span class="p">.</span><span class="na">getCurrent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">CompletableFuture</span><span class="p">.</span><span class="na">supplyAsync</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="n">apiService</span><span class="p">.</span><span class="na">fetchData</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">CompletionException</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}).</span><span class="na">thenAccept</span><span class="p">(</span><span class="n">data</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">showData</span><span class="p">(</span><span class="n">data</span><span class="p">)))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">.</span><span class="na">exceptionally</span><span class="p">(</span><span class="n">ex</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">showError</span><span class="p">(</span><span class="n">ex</span><span class="p">.</span><span class="na">getCause</span><span class="p">().</span><span class="na">getMessage</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="k">return</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">});</span></span></span></code></pre></div></div><p>What is happening here is architecturally remarkable:</p><ul><li><strong>supplyAsync(&hellip;)</strong> starts the call in a separate thread of the Common ForkJoinPool.</li><li>The<strong>fetchData()</strong> runs independently of the UI thread – does not block it.</li><li>The result (or error) is then sent via<strong>UI.access(&hellip;) and</strong> brought back into the server-side context where Vaadin UI components can be safely manipulated.</li></ul><p>This separation of calculation (REST call) and presentation (Vaadin components) corresponds to a classic principle of responsive design, only on the server-side level.</p><h3 id="error-handling-in-the-futures-chain">Error handling in the futures chain</h3><p>Another advantage: Error handling is modelled clearly and separately, via**. exceptionally(&hellip;)** The original exception object is available within this method. In many cases, a targeted display for the user is sufficient; ​​however, logging, metric collection, or retry logic can be added if necessary.</p><h3 id="progress-indicator-and-ui-states">Progress indicator and UI states</h3><p>In asynchronous scenarios, it&rsquo;s recommended to also provide visual feedback about the loading status—for example, through a spinner, a progress bar, or the targeted deactivation of interaction elements. Example:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">refresh</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Neu laden"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">refresh</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">ProgressBar</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">CompletableFuture</span><span class="p">.</span><span class="na">supplyAsync</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">apiService</span><span class="p">.</span><span class="na">fetchData</span><span class="p">())</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="na">thenAccept</span><span class="p">(</span><span class="n">data</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">showData</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">refresh</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">.</span><span class="na">exceptionally</span><span class="p">(</span><span class="n">ex</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">ui</span><span class="p">.</span><span class="na">access</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">showError</span><span class="p">(</span><span class="s">"Loading failed"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">refresh</span><span class="p">.</span><span class="na">setEnabled</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span></span></span></code></pre></div></div><h3 id="combination-with-multiple-requests">Combination with multiple requests</h3><p>Parallel REST calls can also be made by<strong>CompletableFuture.allOf(&hellip;)</strong> Orchestrate. Data from multiple services can be loaded independently and then analysed together. This technique is beneficial for dashboards, complex forms, or multi-API compositions.</p><p>Conclusion of this chapter:<br>
CompletableFuture provides an elegant way to integrate REST calls into Vaadin in a non-blocking manner, without any additional libraries. The control is type-safe and has minimal overhead. Combined with Vaadin&rsquo;s<strong>UI.access(&hellip;)</strong> mechanism, this creates reactive yet deterministic behaviour that is both technically and ergonomically compelling.</p><h2 id="10-conclusion-and-outlook">10. Conclusion and outlook</h2><h3 id="rest-adapters-as-a-stable-bridge-in-service-oriented-architectures">REST adapters as a stable bridge in service-oriented architectures</h3><p>The integration of external REST services into Vaadin Flow applications is far more than a technical detail. It is a conceptual component of a modular, maintainable, and externally communicative application. This blog post has shown how a REST connection<strong>without framework dependencies</strong> can be implemented in a structured and robust manner, purely using the onboard resources of the Java Core JDK from version 11, in idiomatic form for Java 24.</p><p>The focus is on an adapter that has three key features:</p><ol><li><p><strong>Technical clarity:</strong> The adapter takes full responsibility for handling HTTP requests, deserialising JSON, error handling, and optional authentication. The UI layer remains completely decoupled from it.</p></li><li><p><strong>Type safety and predictability:</strong> The response data is cast into records – compact, immutable data structures that ensure readability, consistency and clean debugging.</p></li><li><p><strong>Error robustness and extensibility:</strong> With time limits, logging, retry strategies and asynchronous access, the adapter can be made production-ready – without magic, but with conscious design.</p></li></ol><p>What initially starts as a simple way to “just add an HTTP call” to an application becomes<strong>architecturally viable communication module</strong> This approach is particularly advantageous for applications that grow over time: The REST adapter can evolve into a module that bundles various external systems, maintains version control, remains testable, and still interacts cleanly with the UI.</p><h3 id="outlook-rest-in-modular-and-service-oriented-vaadin-applications">Outlook: REST in modular and service-oriented Vaadin applications</h3><p>With increasing complexity and system size, the need to distribute responsibilities grows: Data storage, business logic, UI, and integrations should be independently deployable, testable, and versionable. A typical architecture then moves toward:</p><ul><li><strong>Backend-first architecture</strong> with Vaadin as UI facade,</li><li><strong>Microservice tailoring</strong> , where each external service is modelled as a port/adapter combination,</li><li><strong>Domain-centric UI</strong> , where views explicitly receive data via REST-controlled use cases,</li><li><strong>Security-by-Design</strong> , where REST communication is secured via tokens, headers and protocol standardisation.</li></ul><p>In all these scenarios, the REST adapter remains a central link – lean, deliberately modelled, but with precise semantics. Precisely because Vaadin Flow doesn&rsquo;t attempt to automatically abstract or generically bind REST, the developer retains maximum control over requests, data structures, lifecycles, and user interaction.</p><h3 id="what-remains">What remains?</h3><p>Anyone building a Vaadin application that consumes REST should not treat REST as a workaround or side path, but as an<strong>equal interface to the world outside the UI</strong>. A stable, testable REST connection is a quality feature. And it can be achieved in Core Java with surprisingly few resources – if it is implemented consciously, structured, and in transparent layers.</p><p>Happy Coding</p>
]]></content:encoded><category>Java</category><category>Uncategorized</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/06/ChatGPT-Image-23.-Juni-2025-22_10_43.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/06/ChatGPT-Image-23.-Juni-2025-22_10_43.png"/><enclosure url="https://svenruppert.com/images/2025/06/ChatGPT-Image-23.-Juni-2025-22_10_43.png" type="image/jpeg" length="0"/></item><item><title>Part II - UrlShortener - first Implementation</title><link>https://svenruppert.com/posts/part-ii-urlshortener-first-implementation/</link><pubDate>Fri, 20 Jun 2025 12:36:28 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/part-ii-urlshortener-first-implementation/</guid><description>1. Introduction to implementation 1.1 Objectives and differentiation from the architectural part The first part of this series focused on theory: We explained why a URL shortener is not just a convenience tool, but a security-relevant element of digital infrastructure. We discussed models for collision detection, entropy distribution, and forwarding logic, as well as analysed architectural variants – from stateless redirect services to domain-specific validation mechanisms.</description><content:encoded>&lt;![CDATA[<h2 id="1-introduction-to-implementation">1. Introduction to implementation</h2><h3 id="11-objectives-and-differentiation-from-the-architectural-part">1.1 Objectives and differentiation from the architectural part</h3><p>The first part of this series focused on theory: We explained why a URL shortener is not just a convenience tool, but a security-relevant element of digital infrastructure. We discussed models for collision detection, entropy distribution, and forwarding logic, as well as analysed architectural variants – from stateless redirect services to domain-specific validation mechanisms.</p><ol><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#1-introduction-to-implementation">1. Introduction to implementation</a><ol><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#1-1-objectives-and-differentiation-from-the-architectural-part">1.1 Objectives and differentiation from the architectural part</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#1-2-technological-guardrails-war-vaadin-core-jdk">1.2 Technological guardrails: WAR, Vaadin, Core JDK</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#1-3-overview-of-the-components">1.3 Overview of the components</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#2-project-structure-and-module-organisation">2. Project structure and module organisation</a><ol><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#2-1-structure-of-a-modular-war-project">2.1 Structure of a modular WAR project</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#2-2-separation-of-domain-api-and-ui-code">2.2 Separation of domain, API and UI code</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#2-3-tooling-und-build-maven-jdk-24-war-plugin">2.3 Tooling und Build (Maven, JDK 24, WAR-Plugin)</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#3-url-encoding-base62-and-id-generation">3. URL encoding: Base62 and ID generation</a><ol><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#3-1-design-of-a-stable-short-link-scheme">3.1 Design of a stable short link scheme</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#3-2-implementation-of-a-base62-encoder">3.2 Implementation of a Base62 encoder</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#3-3-alternatives-random-hashing-custom-aliases">3.3 Alternatives: Random, Hashing, Custom Aliases</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#3-4-implementation-base62encoder-java">3.4 Implementation: Base62Encoder.java</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#3-5-what-is-happening-here-and-why">3.5 What is happening here – and why?</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#4-mapping-store-storage-of-the-mapping">4. Mapping Store: Storage of the mapping</a><ol><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#4-1-interface-design-urlmappingstore">4.1 Interface-Design: UrlMappingStore</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#4-2-in-memory-implementation-with-concurrenthashmap">4.2 In-memory implementation with ConcurrentHashMap</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#4-3-extensibility-for-later-persistence">4.3 Extensibility for later persistence</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#4-4-implementation">4.4 Implementation</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#4-5-why-so">4.5 Why so?</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#5-http-api-with-java-tools">5. HTTP API with Java tools</a><ol><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#5-1-http-server-mit-com-sun-net-httpserver-httpserver">5.1 HTTP-Server mit com.sun.net.httpserver.HttpServer</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#5-2-post-shorten-shorten-url">5.2 POST /shorten: Shorten URL</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#5-3-get-code-redirect-to-the-original-url">5.3 GET /{code}: Redirect to the original URL</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#5-4-implementation">5.4 Implementation</a><ol><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#starting-point-shortenerserver-java">Starting point: ShortenerServer.java</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#post-handler-shortenhandler-java">POST Handler: ShortenHandler.java</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#get-handler-redirecthandler-java">GET-Handler: RedirectHandler.java</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#jsonutils-java-minimal-java-json-without-external-libraries">JsonUtils.java (Minimal Java JSON without external libraries)</a></li></ol></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#5-6-core-java-client-implementation">5.6 Core Java Client Implementation</a></li><li><a href="https://svenruppert.com/2025/06/20/auto-draft/#5-7-summary">5.7 Summary</a></li></ol></li></ol><p>This second part now turns to the concrete implementation. We develop a first working version of a URL shortener in<strong>Java 24</strong> , consciously<strong>without the use of frameworks</strong> such as Spring Boot or Jakarta EE. The goal is to achieve a transparent, modularly structured solution that provides all core functions: URL shortening, secure storage of mappings, HTTP forwarding, and optional presentation via a Vaadin-based user interface.</p><p>Particular attention is paid to the clean separation between encoding, storage, API, and UI. The entire application is delivered as a monolithic artefact – specifically, a<strong>classic WAR file</strong> , which is compatible with standard servlet containers such as Jetty or Tomcat. This decision enables rapid deployment and facilitates onboarding and testability.</p><h3 id="12-technological-guardrails-war-vaadin-core-jdk">1.2 Technological guardrails: WAR, Vaadin, Core JDK</h3><p>The implementation is based on a modern, yet deliberately lean technology stack. Only the built-in tools of the JDK and Vaadin Flow are used as the UI framework. The decision to use Vaadin is based on the requirement to implement interactive administration interfaces without additional JavaScript or separate front-end logic, entirely in Java.</p><p>The project is<strong>a multi-module structure.</strong> The separation between core logic, API layer, and UI remains visible and maintainable in the code. Maven is used as the build tool, supplemented by a WAR packaging plugin that creates a classic servlet deployment structure. The use of Java 24 enables the utilisation of modern language tools, including records, pattern matching, sequenced collections, and virtual threads.</p><p>The goal is a production-oriented, comprehensible implementation that can be used both as a learning resource and as a starting point for further product development.</p><h3 id="13-overview-of-the-components">1.3 Overview of the components</h3><p>The application consists of the following core components:</p><ul><li>A<strong>Base62-Encoder</strong> , which transforms consecutive IDs into URL-compatible short forms</li><li>A<strong>Mapping-Store</strong> , which manages the mapping between the short link and the original URL</li><li>A<strong>REST service</strong> , which allows URL shortening and resolution via redirect</li><li>One<strong>optional UI</strong> based on Vaadin Flow for manual management of mappings<br>
and a<strong>configurable WAR deployment</strong> that integrates all components</li></ul><p>The architecture follows the principle:<strong>“As little as possible, as much as necessary.”</strong> Each part of the application is modular and allows for later splitting if necessary – for example, into separate services for reading, writing or analysis.</p><p>In the next chapter, we will focus on the concrete project structure and the module structure.</p><h2 id="2-project-structure-and-module-organisation">2. Project structure and module organisation</h2><h3 id="21-structure-of-a-modular-war-project">2.1 Structure of a modular WAR project</h3><p>The first executable version of the URL shortener is realised as a monolithic Java application, which is in the form of a classic WAR.<strong>The</strong> project&rsquo;s structure is based on a clear,<strong>layered architecture</strong> , which is prepared for later decomposition. The project is organised modularly, distinguishing between core logic, HTTP interface, and user interface. This separation not only allows for better maintainability but also forms the basis for the service decomposition planned in Part III or IV.</p><p>The project consists of three main modules:</p><ul><li>shortener-core: Contains all business logic, including URL encoding, data model and store interfaces.</li><li>shortener-api: Implements the REST API based on the Java HTTP server (com.sun.net.httpserver.HttpServer).</li><li>shortener-ui-required: Optional UI module with Vaadin Flow for managing and visualising mappings.</li></ul><p>These modules are distributed via a central WAR project (shortener war), which handles the delivery configuration and combines all dependencies. The WAR project is the only one that handles servlet-specific aspects (e.g., web.xml, I require a Servlet) – the remaining modules remain entirely independent of it.</p><h3 id="22-separation-of-domain-api-and-ui-code">2.2 Separation of domain, API and UI code</h3><p>2.2 Separation of Domain, API, and UI Code</p><p>The modularisation of the project is based on the principle of technological isolation: The core business logic must know nothing about HTTP, servlet containers, or UI frameworks. This way, it remains fully testable, interchangeable, and reusable—for example, for future CLI or event-based variants of the shortener.</p><p>The core module defines all central interfaces (UrlMappingStore, ShortCodeEncoder) as well as the base classes (ShortUrlMapping, Base62Encoder). These components do not contain any I/O logic.</p><p>The api module is responsible for parsing HTTP requests, routing, and generating redirects and JSON responses. It accesses the core logic internally but remains detached from UI aspects.</p><p>The ui-vaadin module uses Vaadin Flow to implement a web-based interface, integrates the core logic directly, and is initialised in the WAR via a dedicated servlet definition.</p><p>Additional modules can be optionally added—for example, for persistence, monitoring, or analysis—without compromising the coherence of the structure.</p><h3 id="23-tooling-und-build-maven-jdk-24-war-plugin">2.3 Tooling und Build (Maven, JDK 24, WAR-Plugin)</h3><p>The build system is based on the current version of Maven. Each module is managed as a standalone Maven project with its pom.xml, with shortener-war configured as the parent WAR application. WAR packaging is handled using the standard servlet model, allowing the resulting file to be easily deployed in Tomcat, Jetty, or any Servlet 4.0+ compatible container.</p><p>Java 24 is required at runtime, which is particularly relevant for modern language features such as record, pattern matching, and SequencedMap. Release 21 or higher is recommended as the target platform to ensure compatibility with modern runtimes.</p><p>Vaadin Flow integration is handled purely on the server side via the Vaadin Servlet and does not require a separate front-end build pipeline. Resources such as themes and icons are loaded entirely from the classpath.</p><h2 id="3-url-encoding-base62-and-id-generation">3. URL encoding: Base62 and ID generation</h2><h3 id="31-design-of-a-stable-short-link-scheme">3.1 Design of a stable short link scheme</h3><p>The key requirement for a URL shortener is to generate unique, shortest possible character strings that serve as keys for accessing the original URL. To meet this requirement, the first implementation utilises a sequential ID scheme that assigns a consecutive numeric ID to each new URL. This ID is then converted into a URL-compatible format—specifically, Base62.</p><p>Base62 includes the 26 uppercase letters, the 26 lowercase letters, and the 10 decimal digits. Unlike Base64, Base62 does not contain special characters such as +, /, or =, making it ideal for URLs: The generated strings are readable, system-friendly, and easily transferable in all contexts.</p><p>The resulting scheme is thus based on a two-step process:</p><ul><li>Assigning a unique numeric ID (e.g., 1, 2, 3, &hellip;)</li><li>Converting this ID to a Base62 string (e.g., 1 → b, 2 → c, &hellip;)</li></ul><p>This method guarantees unique and unguessable codes, especially if the ID count does not start at 0 or if codes are additionally shuffled.</p><h3 id="32-implementation-of-a-base62-encoder">3.2 Implementation of a Base62 encoder</h3><p>The<strong>Base62-Encoder</strong> is used as a standalone utility class in the core module. It contains two static methods:</p><ul><li>encode(long value): converts a positive integer to a Base62 string</li><li>decode(String input): converts a Base62 string back to an integer</li></ul><p>The alphabet is defined internally as a constant character string, and the conversion process is carried out purely mathematically, comparable to the representation of a number in another place value system.</p><p>This implementation creates a deterministic, stable, and thread-safe encoder that requires no external libraries. The resulting codes are significantly shorter than the underlying decimal number and contain no special characters—a key advantage for embedded or typed links.</p><p>For exceptional cases—such as custom aliases—the encoder remains optional, as such aliases can be stored directly as separate strings. However, by default, the Base62 encoder is the preferred method.</p><h3 id="33-alternatives-random-hashing-custom-aliases">3.3 Alternatives: Random, Hashing, Custom Aliases</h3><p>In addition to the sequential approach, there are other methods for generating short links that can be considered in later stages of development:</p><ul><li><strong>Random-based tokens</strong> (z. B. UUID, SecureRandom) increase unpredictability, but require collision detection and additional memory overhead.</li><li><strong>The hashing process (e.g., SHA-1 of the destination URL) guarantees stability</strong> but is prone to collisions under high load or identical destination addresses.</li><li><strong>Custom aliases</strong> enable readable, short links (e.g., /helloMax), but require additional checking for collisions, syntactic validity, and protection of reserved terms.</li></ul><p>For the first version, we focus on the sequential model with Base62 transformation – a stable and straightforward approach.</p><h3 id="34-implementation-base62encoderjava">3.4 Implementation: Base62Encoder.java</h3><p>The goal is to provide a simple utility class that converts integers to Base62 strings and vice versa. This class is thread-safe, stateless, and implemented without any external dependencies.</p><p>First, the complete source code:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">Base62Encoder</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">ALPHABET</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">BASE</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ALPHABET</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="nf">Base62Encoder</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">encode</span><span class="p">(</span><span class="kt">long</span><span class="w"/><span class="n">number</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">number</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Only non-negative values supported"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">StringBuilder</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">do</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kt">int</span><span class="w"/><span class="n">remainder</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="w"/><span class="p">(</span><span class="n">number</span><span class="w"/><span class="o">%</span><span class="w"/><span class="n">BASE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">result</span><span class="p">.</span><span class="na">insert</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">ALPHABET</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">reminder</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">number</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">number</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">BASE</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/><span class="k">while</span><span class="w"/><span class="p">(</span><span class="n">number</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">toString</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="nf">decode</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">input</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">input</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">input</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Input must not be null or empty"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kt">long</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">char</span><span class="w"/><span class="n">c</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">input</span><span class="p">.</span><span class="na">toCharArray</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kt">int</span><span class="w"/><span class="n">index</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ALPHABET</span><span class="p">.</span><span class="na">indexOf</span><span class="p">(</span><span class="n">c</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">index</span><span class="w"/><span class="o">==</span><span class="w"/><span class="o">-</span><span class="n">1</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Invalid character in Base62 string: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">c</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">BASE</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">index</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">result</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="35-what-is-happening-here--and-why">3.5 What is happening here – and why?</h3><p>This class encapsulates all Base62 encoding behaviour in two static methods. The character set consists of digits (0–9), lowercase letters (a–z), and uppercase letters (A–Z), resulting in exactly 62 different characters.</p><ul><li><p>The method encode(long number) converts an integer into its inverse representation in the base 62 place value system. The remainder of the division by 62 is successively calculated, and the corresponding character is inserted. The result is a short, URL-friendly string.</p></li><li><p>The decode(String input) method reverses this process: it converts a Base62 string back into its numeric representation. Each character is replaced by its index in the alphabet and weighted accordingly.</p></li></ul><p>This implementation is robust against invalid input, operates entirely in memory, and can be used directly in ID generation or URL mappings.</p><p><strong>Implementation: ShortCodeGenerator.java</strong></p><p>The class abstracts ID generation from the concrete storage mechanism. It is suitable for both the in-memory version and future persistent variants. The generator is thread-safe and uses only JDK resources.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">ShortCodeGenerator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">AtomicLong</span><span class="w"/><span class="n">counter</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="nf">ShortCodeGenerator</span><span class="p">(</span><span class="kt">long</span><span class="w"/><span class="n">initialValue</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">counter</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">AtomicLong</span><span class="p">(</span><span class="n">initialValue</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">nextCode</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kt">long</span><span class="w"/><span class="n">id</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">counter</span><span class="p">.</span><span class="na">getAndIncrement</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">Base62Encoder</span><span class="p">.</span><span class="na">encode</span><span class="p">(</span><span class="n">id</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="nf">currentId</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">counter</span><span class="p">.</span><span class="na">get</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><hr><p><strong>Explanation</strong></p><p>This class encapsulates a sequential counter that is incremented each time nextCode() generates a new, unique short code. The output is based on a monotonically increasing ID encoded into a Base62 string.</p><p>The getAndIncrement() method of AtomicLong is<strong>non-blocking</strong> and thus highly performant, even under high concurrency conditions. The generated code is unambiguous, compact, and deterministic—properties that are ideal for auditing, logging, and subsequent analysis.</p><p>The constructor allows you to configure the initial value. This is useful, for example, if you want to continue a persistent counter after a restart.</p><p><strong>Example usage (e.g. in the mapping store)</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">ShortCodeGenerator</span><span class="w"/><span class="n">generator</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ShortCodeGenerator</span><span class="p">(</span><span class="n">1L</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">String</span><span class="w"/><span class="n">shortCode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">generator</span><span class="p">.</span><span class="na">nextCode</span><span class="p">();</span><span class="w"/><span class="o">//</span><span class="w"/><span class="n">z</span><span class="p">.</span><span class="w"> </span><span class="n">B</span><span class="p">.</span><span class="w"/><span class="s">"b"</span><span class="p">,</span><span class="w"/><span class="s">"c"</span><span class="p">,</span><span class="w"/><span class="s">"d"</span><span class="p">,</span><span class="w"/><span class="p">...</span></span></span></code></pre></div></div><h2 id="4-mapping-store-storage-of-the-mapping">4. Mapping Store: Storage of the mapping</h2><h3 id="41-interface-design-urlmappingstore">4.1 Interface-Design: UrlMappingStore</h3><p>The mapping store forms the heart of the URL shortener. It manages the mapping between a short code (e.g.kY7zD) and the corresponding target URL (<a href="https://example.org/foo/bar%29">https://example.org/foo/bar)</a>. At the same time, it takes control over multiple uses, expiration times, and potential aliases.</p><p>In the first stage of development, a purely<strong>in-memory-based solution</strong> is used**.** This is fast, simple, and ideal for starting—even if it is lost upon reboot. Persistence is deliberately postponed to a later phase.</p><p>The store is abstracted via a simple interface. This interface allows for later substitution (e.g., with a file- or database-based version) without affecting the API or UI components.</p><h3 id="42-in-memory-implementation-with-concurrenthashmap">4.2 In-memory implementation with ConcurrentHashMap</h3><p>The first concrete implementation utilises a ConcurrentHashMap to ensure reliable access even under heavy load. Each mapping entry is represented by a simple record object (ShortUrlMapping) that contains the target URL, creation time, and optional expiration information.</p><p>The combination of ConcurrentHashMap and ShortCodeGenerator allows deterministic and thread-safe ID assignment without the need for explicit synchronisation. This creates a high-performance solution that operates reliably even under high load.</p><h3 id="43-extensibility-for-later-persistence">4.3 Extensibility for later persistence</h3><p>All entries are accessible via a central interface. This interface is used not only for storage and retrieval but also forms the basis for later extensions, such as a persistence layer with flat files or EclipseStore, process control via TTL, or even event-driven backends.</p><p>The data structure can be extended with metrics, validity logic, or audit fields without requiring changes to the API – a classic approach to interface orientation in the sense of the open/closed principles.</p><h3 id="44-implementation">4.4 Implementation</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public record ShortUrlMapping(</span></span><span class="line"><span class="cl">   String shortCode,</span></span><span class="line"><span class="cl">   String originalUrl,</span></span><span class="line"><span class="cl">   Instant createdAt,</span></span><span class="line"><span class="cl">   Optional<span class="nt">&lt;Instant&gt;</span> expiresAt</span></span><span class="line"><span class="cl">) {}</span></span></code></pre></div></div><p>This structure represents the basic assignment and allows the optional specification of an expiration time.</p><p><strong>Store-Interface: UrlMappingStore.java</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public interface UrlMappingStore {</span></span><span class="line"><span class="cl"> ShortUrlMapping createMapping(String originalUrl);</span></span><span class="line"><span class="cl"> Optional<span class="nt">&lt;ShortUrlMapping&gt;</span> findByShortCode(String shortCode);</span></span><span class="line"><span class="cl"> boolean exists(String shortCode);</span></span><span class="line"><span class="cl"> List<span class="nt">&lt;ShortUrlMapping&gt;</span> findAll();</span></span><span class="line"><span class="cl"> boolean delete(String shortCode);</span></span><span class="line"><span class="cl"> int mappingCount();</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The interface is deliberately kept slim and abstracts the two core operations: insert (with creation) and lookup.</p><p><strong>Implementation: InMemoryUrlMappingStore.java</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class InMemoryUrlMappingStore</span></span><span class="line"><span class="cl">   implements UrlMappingStore, HasLogger {</span></span><span class="line"><span class="cl"> private final ConcurrentHashMap<span class="nt">&lt;String</span><span class="err">,</span><span class="err">ShortUrlMapping</span><span class="nt">&gt;</span> store </span></span><span class="line"><span class="cl">                    = new ConcurrentHashMap<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> private final ShortCodeGenerator generator;</span></span><span class="line"><span class="cl"> public InMemoryUrlMappingStore() {</span></span><span class="line"><span class="cl">   this.generator = new ShortCodeGenerator(1L);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public ShortUrlMapping createMapping(String originalUrl) {</span></span><span class="line"><span class="cl">   logger().info("originalUrl: {} -&gt;", originalUrl);</span></span><span class="line"><span class="cl">   String code = generator.nextCode();</span></span><span class="line"><span class="cl">   ShortUrlMapping shortMapping = new ShortUrlMapping(</span></span><span class="line"><span class="cl">       code,</span></span><span class="line"><span class="cl">       originalUrl,</span></span><span class="line"><span class="cl">       Instant.now(),</span></span><span class="line"><span class="cl">       Optional.empty()</span></span><span class="line"><span class="cl">   );</span></span><span class="line"><span class="cl">   store.put(code, shortMapping);</span></span><span class="line"><span class="cl">   return  shortMapping;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public Optional<span class="nt">&lt;ShortUrlMapping&gt;</span> findByShortCode(String shortCode) {</span></span><span class="line"><span class="cl">   return Optional.ofNullable(store.get(shortCode));</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public boolean exists(String shortCode) {</span></span><span class="line"><span class="cl">   return store.containsKey(shortCode);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public List<span class="nt">&lt;ShortUrlMapping&gt;</span> findAll() {</span></span><span class="line"><span class="cl">   return new ArrayList<span class="err">&lt;</span>&gt;(store.values());</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public boolean delete(String shortCode) {</span></span><span class="line"><span class="cl">   return store.remove(shortCode) != null;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public int mappingCount() {</span></span><span class="line"><span class="cl">   return store.size();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h3 id="45-why-so">4.5 Why so?</h3><p>The use of a ConcurrentHashMap ensures that concurrent write and read operations can be handled consistently and efficiently. The combination with AtomicLong in ShortCodeGenerator prevents collisions. The interface allows for a persistent implementation to be introduced later without changing the API or UI behaviour.</p><h2 id="5-http-api-with-java-tools">5. HTTP API with Java tools</h2><h3 id="51-http-server-mit-comsunnethttpserverhttpserver">5.1 HTTP-Server mit com.sun.net.httpserver.HttpServer</h3><p>Instead of relying on heavyweight frameworks like Spring or Jakarta EE, we use the<strong>lightweight HTTP server implementation</strong> that the JDK already includes in the package com.sun.net.httpserver. This API, although rudimentary, is performant, stable, and perfectly sufficient for our use case.</p><p>The server is configured in just a few lines, requires no XML or annotation-based mappings, and can be controlled entirely programmatically. For each path, we define a separate HTTP handler that receives the request, processes it, and returns a structured HTTP response.</p><h3 id="52-post-shorten-shorten-url">5.2 POST /shorten: Shorten URL</h3><p>The first endpoint allows a long URL to be passed over an<strong>HTTP POST</strong> to the shortener. In response, the server returns the generated short form, in the simplest case as a JSON object with the shortCode.</p><p><strong>Example request:</strong></p><p>POST /shorten</p><p><strong>Content-Type: application/json</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">json</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span></span></span><span class="line"><span class="cl">  <span class="nt">"url"</span><span class="p">:</span><span class="s2">"https://example.com/some/very/long/path"</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Answer:</strong></p><p><strong>200 OK</strong></p><p><strong>Content-Type: application/json</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">json</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span></span></span><span class="line"><span class="cl">  <span class="nt">"shortCode"</span><span class="p">:</span><span class="s2">"kY7zD"</span></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Missing or invalid entries are marked with 400 Bad Request answered.</p><h3 id="53-get-code-redirect-to-the-original-url">5.3 GET /{code}: Redirect to the original URL</h3><p>When calling a shortcode (e.g.GET /kY7zD), the server checks whether a mapping exists. If so, a HTTP 302 redirect to the original address. If the code is unknown or expired, a 404 Not Found error will be displayed.</p><p>This redirection is stateless and allows for later isolation into a read-only redirect service.</p><h3 id="54-implementation">5.4 Implementation</h3><h4 id="starting-point-shortenerserverjava">Starting point: ShortenerServer.java</h4><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ShortenerServer</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="n">HttpServer</span><span class="w"/><span class="n">server</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">new</span><span class="w"/><span class="n">ShortenerServer</span><span class="p">().</span><span class="na">init</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">heat</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">our</span><span class="w"/><span class="n">store</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">InMemoryUrlMappingStore</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">server</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">HttpServer</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">InetSocketAddress</span><span class="p">(</span><span class="n">8080</span><span class="p">),</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">server</span><span class="p">.</span><span class="na">createContext</span><span class="p">(</span><span class="s">"/shorten"</span><span class="p">,</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ShortenHandler</span><span class="p">(</span><span class="n">store</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">server</span><span class="p">.</span><span class="na">createContext</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RedirectHandler</span><span class="p">(</span><span class="n">store</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">server</span><span class="p">.</span><span class="na">setExecutor</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w"/><span class="c1">// default executor</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">server</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"URL Shortener server running at http://localhost:8080"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">shutdown</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">server</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">server</span><span class="p">.</span><span class="na">stop</span><span class="p">(</span><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"URL Shortener server stopped"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><hr><h4 id="post-handler-shortenhandlerjava">POST Handler: ShortenHandler.java</h4><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ShortenHandler</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">implements</span><span class="w"/><span class="n">HttpHandler</span><span class="p">,</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">UrlMappingStorestore</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="nf">ShortenHandler</span><span class="p">(</span><span class="n">UrlMappingStore</span><span class="w"/><span class="n">store</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">store</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">store</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handle</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">exchange</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">"POST"</span><span class="p">.</span><span class="na">equalsIgnoreCase</span><span class="p">(</span><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestMethod</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">405</span><span class="p">,</span><span class="w"/><span class="o">-</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">InputStream</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestBody</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">payload</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">parseJson</span><span class="p">(</span><span class="n">body</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">String</span><span class="w"/><span class="n">originalUrl</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">payload</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"url"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Received request to shorten url: {}"</span><span class="p">,</span><span class="w"/><span class="n">originalUrl</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">originalUrl</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">originalUrl</span><span class="p">.</span><span class="na">isBlank</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">400</span><span class="p">,</span><span class="w"/><span class="o">-</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">ShortUrlMapping</span><span class="w"/><span class="n">mapping</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">store</span><span class="p">.</span><span class="na">createMapping</span><span class="p">(</span><span class="n">originalUrl</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Created mapping for {} -&gt; {}"</span><span class="p">,</span><span class="w"/><span class="n">originalUrl</span><span class="p">,</span><span class="w"/><span class="n">mapping</span><span class="p">.</span><span class="na">shortCode</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">response</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">toJson</span><span class="p">(</span><span class="n">Map</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="s">"shortCode"</span><span class="p">,</span><span class="w"/><span class="n">mapping</span><span class="p">.</span><span class="na">shortCode</span><span class="p">())).</span><span class="na">getBytes</span><span class="p">(</span><span class="n">StandardCharsets</span><span class="p">.</span><span class="na">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseHeaders</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span><span class="w"/><span class="s">"application/json"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">200</span><span class="p">,</span><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">length</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseBody</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">response</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h4 id="get-handler-redirecthandlerjava">GET-Handler: RedirectHandler.java</h4><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class RedirectHandler</span></span><span class="line"><span class="cl">   implements HttpHandler , HasLogger {</span></span><span class="line"><span class="cl"> private final UrlMappingStorestore;</span></span><span class="line"><span class="cl"> public RedirectHandler(UrlMappingStore store) {</span></span><span class="line"><span class="cl">   this.store = store;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void handle(HttpExchange exchange)</span></span><span class="line"><span class="cl">     throws IOException {</span></span><span class="line"><span class="cl">   our requestURI = exchange.getRequestURI();</span></span><span class="line"><span class="cl">   our fullPath = requestURI.getPath();</span></span><span class="line"><span class="cl">   logger().info("Full path: {}", fullPath);</span></span><span class="line"><span class="cl">   String path = fullPath.substring(1); // strip leading '/'</span></span><span class="line"><span class="cl">   logger().info("Path: {}", path);</span></span><span class="line"><span class="cl">   if (path.isEmpty()) {</span></span><span class="line"><span class="cl">     exchange.sendResponseHeaders(400, -1);</span></span><span class="line"><span class="cl">     return;</span></span><span class="line"><span class="cl">   }</span></span><span class="line"><span class="cl">   Optional<span class="nt">&lt;String&gt;</span> target = store</span></span><span class="line"><span class="cl">       .findByShortCode(path)</span></span><span class="line"><span class="cl">       .map(ShortUrlMapping::originalUrl);</span></span><span class="line"><span class="cl">   if (target.isPresent()) {</span></span><span class="line"><span class="cl">     exchange.getResponseHeaders().add("Location", target.get());</span></span><span class="line"><span class="cl">     exchange.sendResponseHeaders(302, -1);</span></span><span class="line"><span class="cl">   } else {</span></span><span class="line"><span class="cl">     exchange.sendResponseHeaders(404, -1);</span></span><span class="line"><span class="cl">   }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h4 id="jsonutilsjava-minimal-java-json-without-external-libraries">JsonUtils.java (Minimal Java JSON without external libraries)</h4><p>Since we do not want to use external dependencies such as Jackson or Gson in this first implementation, we need our<strong>own utility class</strong> to process simple JSON objects, specifically:</p><ul><li>String → Map&lt;String, String&gt;: for processing HTTP POST payloads (/shorten)</li><li>Map&lt;String, String&gt; → JSON-String: to generate responses (e.g.{ &ldquo;shortCode&rdquo;: &ldquo;abc123&rdquo; })</li></ul><p>This class is sufficient for simple key-value structures, as used in the shortener. It is<strong>not intended for nested objects or arrays</strong> , but as a pragmatic solution for the start.</p><p><strong>Implementation: JsonUtils.java</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">JsonUtils</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="nf">JsonUtils</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="nf">parseJson</span><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">input</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">String</span><span class="w"/><span class="n">json</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">readInputStream</span><span class="p">(</span><span class="n">input</span><span class="p">).</span><span class="na">trim</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">parseJson</span><span class="p">(</span><span class="n">json</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="nd">@NotNull</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="nf">parseJson</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">json</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">json</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="s">"{"</span><span class="p">)</span><span class="w"/><span class="o">||</span><span class="w"/><span class="o">!</span><span class="n">json</span><span class="p">.</span><span class="na">endsWith</span><span class="p">(</span><span class="s">"}"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Invalid JSON object"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">// Remove curly braces</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">String</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">json</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">json</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"/><span class="o">-</span><span class="w"/><span class="n">1</span><span class="p">).</span><span class="na">trim</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">body</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="n">Collections</span><span class="p">.</span><span class="na">emptyMap</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">// Separate key-value pairs with commas</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">entries</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">body</span><span class="p">.</span><span class="na">split</span><span class="p">(</span><span class="s">","</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">Arrays</span><span class="p">.</span><span class="na">stream</span><span class="p">(</span><span class="n">entries</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">entry</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">entry</span><span class="p">.</span><span class="na">split</span><span class="p">(</span><span class="s">":"</span><span class="p">,</span><span class="w"/><span class="n">2</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">parts</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">parts</span><span class="p">.</span><span class="na">length</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">2</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">forEachOrdered</span><span class="p">(</span><span class="n">parts</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">String</span><span class="w"/><span class="n">key</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">unquote</span><span class="p">(</span><span class="n">parts</span><span class="o">[</span><span class="n">0</span><span class="o">]</span><span class="p">.</span><span class="na">trim</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">String</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">unquote</span><span class="p">(</span><span class="n">parts</span><span class="o">[</span><span class="n">1</span><span class="o">]</span><span class="p">.</span><span class="na">trim</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">result</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">value</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">result</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toJson</span><span class="p">(</span><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">map</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">StringBuilder</span><span class="w"/><span class="n">sb</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">sb</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="s">"{"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kt">boolean</span><span class="w"/><span class="n">first</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">Map</span><span class="p">.</span><span class="na">Entry</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">entry</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">map</span><span class="p">.</span><span class="na">entrySet</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">first</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">sb</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="s">","</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">sb</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="s">"\""</span><span class="p">).</span><span class="na">append</span><span class="p">(</span><span class="n">escape</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="na">getKey</span><span class="p">())).</span><span class="na">append</span><span class="p">(</span><span class="s">"\":"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">sb</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="s">"\""</span><span class="p">).</span><span class="na">append</span><span class="p">(</span><span class="n">escape</span><span class="p">(</span><span class="n">entry</span><span class="p">.</span><span class="na">getValue</span><span class="p">())).</span><span class="na">append</span><span class="p">(</span><span class="s">"\""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">first</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">sb</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="s">"}"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">sb</span><span class="p">.</span><span class="na">toString</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">readInputStream</span><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">input</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">BufferedReader</span><span class="w"/><span class="n">reader</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">BufferedReader</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">new</span><span class="w"/><span class="n">InputStreamReader</span><span class="p">(</span><span class="n">input</span><span class="p">,</span><span class="w"/><span class="n">StandardCharsets</span><span class="p">.</span><span class="na">UTF_8</span><span class="p">)))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="n">reader</span><span class="p">.</span><span class="na">lines</span><span class="p">().</span><span class="na">collect</span><span class="p">(</span><span class="n">joining</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">unquote</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">s</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="s">"\""</span><span class="p">)</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">endsWith</span><span class="p">(</span><span class="s">"\""</span><span class="p">)</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"/><span class="o">&gt;=</span><span class="w"/><span class="n">2</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"/><span class="o">-</span><span class="w"/><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">s</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">escape</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">s</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">// simple escape logic for quotes</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">return</span><span class="w"/><span class="n">s</span><span class="p">.</span><span class="na">replace</span><span class="p">(</span><span class="s">"\""</span><span class="p">,</span><span class="w"/><span class="s">"\\\""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Properties and limitations</strong></p><p>This implementation is:</p><ul><li><strong>fully JDK-based</strong>(no third-party libraries)</li><li>for<strong>flat JSON objects</strong> suitable, i.e.{ &ldquo;key&rdquo;: &ldquo;value&rdquo; }</li><li><strong>robust against trivial parsing errors</strong> , but without JSON Schema validation</li><li>Consciously<strong>minimalistic</strong> to stay within the scope of the prototype</li></ul><p>It is sufficient for:</p><ul><li>POST /shorten(Client sends{ &ldquo;url&rdquo;: &ldquo;&hellip;&rdquo; })</li><li>Response to this POST (server sends{ &ldquo;shortCode&rdquo;: &ldquo;&hellip;&rdquo; })</li></ul><p><strong>Example use</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">InputStream</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestBody</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Map</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">input</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">JsonUtils</span><span class="p">.</span><span class="na">parseJson</span><span class="p">(</span><span class="n">body</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">String</span><span class="w"/><span class="n">shortCode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"abc123"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">String</span><span class="w"/><span class="n">response</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">JsonUtils</span><span class="p">.</span><span class="na">toJson</span><span class="p">(</span><span class="n">Map</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="s">"shortCode"</span><span class="p">,</span><span class="w"/><span class="n">shortCode</span><span class="p">));</span></span></span></code></pre></div></div><p>For productive systems, the following is recommended in the future:</p><ul><li><strong>Gson</strong>(lightweight, idiomatic)</li><li><strong>Jackson</strong>(extensive, also for DTO binding)</li><li><strong>Json-B</strong>(Standard-API, Jakarta conform)</li></ul><p>However, for our first implementation in the Core JDK, the solution shown above deliberately remains the appropriate middle ground.</p><h3 id="56-core-java-client-implementation">5.6 Core Java Client Implementation</h3><p>The class URLShortenerClient functions as a minimalist HTTP client for interacting with a URL shortener service. Its structure allows connection to a configurable or locally running server, with the default address being<a href="http://localhost:8080/">http://localhost:8080/</a>. This enables easy integration into local development environments, test runs, or automated system tests without the need for additional configuration.</p><p>At the heart of the functionality is the method shortenURL(String originalUrl). It initiates an HTTP POST call against the server endpoint/shorten, transmits the URL to be shortened in a simple JSON document and immediately evaluates the server&rsquo;s response. Successful completion is indicated exclusively by a status code.200 OK In this case, the method extracts the contained shortCode using the static auxiliary method extractShortCode() from the class JsonUtils. If the server returns a different HTTP code instead, the process will be aborted with a corresponding IOException, which enforces explicit error handling at the application level. This maintains a clear semantic separation between regular usage and exception situations.</p><p>The second central method,resolveShortcode(String shortCode), is used to explicitly resolve a short URL. It sends a GET request directly to the server&rsquo;s root context, supplemented by the passed code. The behaviour of this method largely corresponds to that of a web browser, with the difference that automatic redirects have been deliberately deactivated. This way, the method can determine the actual target address, if present, ​​from the HTTP header field. Location and return it as a result. It clearly distinguishes between valid redirects (status 301 or 302), non-existent codes (status 404), and other unexpected responses. In the latter case, an IOException is thrown, analogous to the shortening process.</p><p>Technically speaking, the URLShortenerClient is exclusively composed of the Java SE API, namely HttpURLConnection, TYPE and stream-based input and output routines. All communication is UTF-8 encoded, ensuring high interoperability with modern JSON-based REST interfaces. The class also implements the interface HasLogger, which suggests a project-wide logging infrastructure and implicitly supports good traceability in server communication.</p><p>This client is particularly recommended for integration tests, command-line tools, or administrative scripts that require specific URLs to be shortened or verified. Due to its lean structure, the class is also suitable as a starting point for further abstractions, such as service-oriented encapsulation in larger architectures.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">URLShortenerClient</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">protected</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">DEFAULT_SERVER_PORT</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"8080"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">protected</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">DEFAULT_SERVER_URL</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"http://localhost:"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">DEFAULT_SERVER_PORT</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">protected</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">SHORTEN_URL_ENDPOINT</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"/shorten"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">protected</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">REDIRECT_URL_ENDPOINT</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"/"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">TYPE</span><span class="w"/><span class="n">serverBase</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="nf">URLShortenerClient</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">serverBaseUrl</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">serverBase</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">TYPE</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="n">serverBaseUrl</span><span class="p">.</span><span class="na">endsWith</span><span class="p">(</span><span class="s">"/"</span><span class="p">)</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">serverBaseUrl</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">serverBaseUrl</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"/"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="nf">URLShortenerClient</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">this</span><span class="p">.</span><span class="na">serverBase</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">TYPE</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="n">DEFAULT_SERVER_URL</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm">  * String originalUrl = "https://svenruppert.com";</span></span></span><span class="line"><span class="cl"><span class="cm">  *</span></span></span><span class="line"><span class="cl"><span class="cm">  * @param originalUrl</span></span></span><span class="line"><span class="cl"><span class="cm">  * @return</span></span></span><span class="line"><span class="cl"><span class="cm">  * @throws IOException</span></span></span><span class="line"><span class="cl"><span class="cm">  */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">shortenURL</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">originalUrl</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">our</span><span class="w"/><span class="n">serverURL</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serverBase</span><span class="p">.</span><span class="na">toURL</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="c1">// --- Step 1: POST to the /shorten endpoint with a valid URL ---</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">URL</span><span class="w"/><span class="n">shortenUrl</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">URI</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="n">serverURL</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">SHORTEN_URL_ENDPOINT</span><span class="p">).</span><span class="na">toURL</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">HttpURLConnection</span><span class="w"/><span class="n">connection</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">HttpURLConnection</span><span class="p">)</span><span class="w"/><span class="n">shortenUrl</span><span class="p">.</span><span class="na">openConnection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">connection</span><span class="p">.</span><span class="na">setRequestMethod</span><span class="p">(</span><span class="s">"POST"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">connection</span><span class="p">.</span><span class="na">setDoOutput</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">connection</span><span class="p">.</span><span class="na">setRequestProperty</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span><span class="w"/><span class="s">"application/json"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">String</span><span class="w"/><span class="n">body</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"{\"url\":\""</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">originalUrl</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"\"}"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getOutputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">body</span><span class="p">.</span><span class="na">getBytes</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kt">int</span><span class="w"/><span class="n">status</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getResponseCode</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">status</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">200</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">is</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">String</span><span class="w"/><span class="n">jsonResponse</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">String</span><span class="p">(</span><span class="n">is</span><span class="p">.</span><span class="na">readAllBytes</span><span class="p">(),</span><span class="w"/><span class="n">UTF_8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">String</span><span class="w"/><span class="n">extractedShortCode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">JsonUtils</span><span class="p">.</span><span class="na">extractShortCode</span><span class="p">(</span><span class="n">jsonResponse</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"extractedShortCode .. {}"</span><span class="p">,</span><span class="w"/><span class="n">extractedShortCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="w"/><span class="n">extractedShortCode</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Server returned status "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">status</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">resolveShortcode</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">shortCode</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Resolving shortCode: {}"</span><span class="p">,</span><span class="w"/><span class="n">shortCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">URL</span><span class="w"/><span class="n">url</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">URI</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="n">DEFAULT_SERVER_URL</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">REDIRECT_URL_ENDPOINT</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">shortCode</span><span class="p">).</span><span class="na">toURL</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"url .. {}"</span><span class="p">,</span><span class="w"/><span class="n">url</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">HttpURLConnection</span><span class="w"/><span class="n">connection</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">HttpURLConnection</span><span class="p">)</span><span class="w"/><span class="n">url</span><span class="p">.</span><span class="na">openConnection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">connection</span><span class="p">.</span><span class="na">setInstanceFollowRedirects</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kt">int</span><span class="w"/><span class="n">responseCode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getResponseCode</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"responseCode .. {}"</span><span class="p">,</span><span class="w"/><span class="n">responseCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">responseCode</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">302</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">responseCode</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">301</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">our</span><span class="w"/><span class="n">location</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">connection</span><span class="p">.</span><span class="na">getHeaderField</span><span class="p">(</span><span class="s">"Location"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"location .. {}"</span><span class="p">,</span><span class="w"/><span class="n">location</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="n">location</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">responseCode</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">404</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IOException</span><span class="p">(</span><span class="s">"Unexpected response: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">responseCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="57-summary">5.7 Summary</h3><p>With just a few lines, you can create a functional HTTP API that serves as an ideal testbed and proof of concept. The structure is minimal but open to extensions such as error objects, rate limiting, or logging. What&rsquo;s particularly remarkable is that the entire API works without servlet containers, external frameworks, or reflection—ideal for embedded applications and lightweight deployments.</p><p>In the next blog post, we will create a graphical interface to map user interactions.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><media:content url="https://svenruppert.com/images/2025/06/ChatGPT-Image-20.-Juni-2025-12_00_13.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/06/ChatGPT-Image-20.-Juni-2025-12_00_13.png"/><enclosure url="https://svenruppert.com/images/2025/06/ChatGPT-Image-20.-Juni-2025-12_00_13.png" type="image/jpeg" length="0"/></item><item><title>Short links, clear architecture – A URL shortener in Core Java</title><link>https://svenruppert.com/posts/short-links-clear-architecture-a-url-shortener-in-core-java/</link><pubDate>Tue, 10 Jun 2025 22:43:22 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/short-links-clear-architecture-a-url-shortener-in-core-java/</guid><description>A URL shortener seems harmless – but if implemented incorrectly, it opens the door to phishing, enumeration, and data leakage. In this first part, I&rsquo;ll explore the theoretical and security-relevant fundamentals of a URL shortener in Java – without any frameworks, but with a focus on entropy, collision tolerance, rate limiting, validity logic, and digital responsibility. The second part covers the complete implementation: modular, transparent, and as secure as possible.</description><content:encoded>&lt;![CDATA[<p>A URL shortener seems harmless – but if implemented incorrectly, it opens the door to phishing, enumeration, and data leakage. In this first part, I&rsquo;ll explore the theoretical and security-relevant fundamentals of a URL shortener in Java – without any frameworks, but with a focus on entropy, collision tolerance, rate limiting, validity logic, and digital responsibility. The second part covers the complete implementation: modular, transparent, and as secure as possible.</p><ol><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#1-1-motivation-and-use-cases">1.1 Motivation and use cases</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#1-2-differentiation-from-related-technologies">1.2 Differentiation from related technologies</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#1-3-objective-of-the-paper">1.3 Objective of the paper</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#2-1-uri-url-and-urn-conceptual-basics">2.1 URI, URL and URN – conceptual basics</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#2-2-principles-of-address-shortening">2.2 Principles of address shortening</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#2-3-entropy-collisions-and-permutation-spaces">2.3 Entropy, collisions and permutation spaces</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#4-2-url-encoding-hashing-base62-and-alternatives">4.2 URL Encoding: Hashing, Base62 and Alternatives</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#4-3-mapping-store-interface-implementation-synchronisation">4.3 Mapping Store: Interface, Implementation, Synchronisation</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#4-4-rest-api-with-pure-java-http-server-handler-routing">4.4 REST API with pure Java (HTTP server, handler, routing)</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#4-5-error-handling-logging-and-monitoring">4.5 Error handling, logging and monitoring</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#5-1-abuse-opportunities-and-protection-mechanisms">5.1 Abuse opportunities and protection mechanisms</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#5-2-rate-limiting-and-ip-based-throttling">5.2 Rate limiting and IP-based throttling</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#5-3-validity-period-and-deletion-concepts">5.3 Validity period and deletion concepts</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#5-4-protection-against-enumeration-and-information-leakage">5.4 Protection against enumeration and information leakage</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#6-1-access-times-and-hash-lookups">6.1 Access times and hash lookups</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#6-2-memory-usage-and-garbage-collection">6.2 Memory usage and garbage collection</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#6-3-benchmarking-local-tests-and-load-simulation">6.3 Benchmarking: Local tests and load simulation</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#7-1-custom-aliases">7.1 Custom aliases</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#7-2-access-counting-and-analytics">7.2 Access counting and analytics</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#7-3-qr-code-integration">7.3 QR-Code-Integration</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#7-4-integration-into-messaging-or-tracking-systems">7.4 Integration into messaging or tracking systems</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#8-1-data-protection-for-link-tracking">8.1 Data protection for link tracking</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#8-2-responsibility-for-forwarding">8.2 Responsibility for forwarding</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#8-3-transparency-and-disclosure-of-the-destination-address">8.3 Transparency and disclosure of the destination address</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#9-1-lessons-learned">9.1 Lessons Learned</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#9-2-possible-further-developments-e-g-blockchain-dnssec">9.2 Possible further developments (e.g. blockchain, DNSSEC)</a></li><li><a href="https://svenruppert.com/2025/06/10/short-links-clear-architecture-a-url-shortener-in-core-java/#9-3-importance-of-url-shorteners-in-the-context-of-digital-sovereignty">9.3 Importance of URL shorteners in the context of digital sovereignty</a></li></ol><h3 id="11-motivation-and-use-cases">1.1 Motivation and use cases</h3><p>In an increasingly fragmented and mobile information world, URLs are not just technical addressing mechanisms; they are central building blocks of digital communication. Long and hard-to-remember URLs are a hindrance in social media, emails, or QR codes, as they are not only aesthetically unappealing but also prone to errors when manually entered. URL shorteners address this problem by generating compact representations that point to the original target address. In addition to improved readability, aspects such as statistical analysis, access control, and campaign tracking also play a key role.</p><p>Initially popularised by services like TinyURL or bit.ly, URL shorteners have now become integrated into many technical infrastructures – from marketing platforms and messaging systems to IoT applications, where storage and bandwidth restrictions play a significant role. A shortened representation of URLs is also a clear advantage in the context of QR codes or limited character sets (e.g., in SMS or NFC data sets).</p><h3 id="12-differentiation-from-related-technologies">1.2 Differentiation from related technologies</h3><p>A URL shortener is not a classic forwarding platform and is conceptually different from proxy systems, link resolvers, or load balancers. While the latter often operate at the transport or application layer (Layer 4 or Layer 7 in the OSI model) and optimise transparency, availability, or performance, a shortener primarily pursues the goal of simplifying the display and management of URLs. Nevertheless, there are overlaps, particularly in the analysis of access patterns and the configuration of redirect policies.</p><p>In this work, a minimalist URL shortener is designed and implemented. It deliberately avoids external frameworks to implement the central concepts in a comprehensible and transparent manner in Core Java. The choice of Java 24 enables the integration of modern language features, such as records, sealed types, and virtual threads, into a secure and robust architecture.</p><h3 id="13-objective-of-the-paper">1.3 Objective of the paper</h3><p>This paper serves a dual purpose: on the one hand, it aims to provide a deep technical understanding of the functionality and challenges associated with a URL shortener. On the other hand, it serves as a practical guide for implementing such a service using pure Java—that is, without Spring, Jakarta EE, or external libraries.</p><p>To this end, a comprehensive architecture will be developed, implemented, and continually enhanced with key aspects such as security, performance, and extensibility. The focus is deliberately on a system-level analysis of the processes to provide developers with a deeper understanding of the interaction between the network layer, coding strategies, and persistent storage. The goal is to develop a viable model that can be utilised in both educational contexts and as a basis for productive services.</p><h2 id="2-technical-background">2. Technical background</h2><h3 id="21-uri-url-and-urn--conceptual-basics">2.1 URI, URL and URN – conceptual basics</h3><p>In everyday language, terms such as &ldquo;URL&rdquo; and &ldquo;link&rdquo; are often used synonymously, although in a technical sense, they describe different concepts.<strong>URI (Uniform Resource Identifier)</strong> refers to any character string that can uniquely name or locate a resource. A URL**(Uniform Resource Locator)** is a special form of a URI that not only identifies but also describes the access path, for example, through a protocol such as https, ftp, or mailto. A<strong>URN (Uniform Resource Name),</strong> on the other hand, names a resource persistently without referring to its physical address, such as urn:isbn:978-3-16-148410-0.</p><p>In the context of URL shorteners, URLs are exclusively concerned with accessible paths, typically via HTTP or HTTPS. The challenge is to transform these access paths in a way that preserves their semantics while reducing their representation.</p><h3 id="22-principles-of-address-shortening">2.2 Principles of address shortening</h3><p>The core idea of ​​a URL shortener is to replace a long URL string with a shorter key that points to the original address via a mapping. This mapping is done either directly in a lookup store (e.g., hash map, database table) or indirectly via a computational method (e.g., a hash function with collision management).</p><p>The goal is to use the redundancy of long URLs to map their entropy to a significantly shorter string. This poses a trade-off between collision-freeness, brevity, and readability. Conventional methods are based on encoding unique keys in a Base62 alphabet ([0-9a-zA-Z]), which offers 62 states per character. Just six characters can represent over 56 billion unique URLs—sufficient for many productive applications.</p><p>The shortcode acts as the primary key for address resolution. It is crucial that it is stable, efficiently generated, and as challenging to guess as possible to prevent misuse (e.g., brute-force enumeration).</p><h3 id="23-entropy-collisions-and-permutation-spaces">2.3 Entropy, collisions and permutation spaces</h3><p>A key aspect of URL shortening is the question of how many different short addresses a system can actually generate. This consideration directly depends on the length of the generated shortcuts and their character set. Many URL shorteners use a so-called Base62 alphabet. This includes the ten digits from zero to nine, the 26 lowercase letters, and the 26 uppercase letters, for a total of 62 different characters.</p><p>For example, if you generate abbreviations with a fixed length of six characters, you get a combinatorial space in which over 56 billion different character strings are possible. Even with this relatively short number of characters, billions of unique URLs can be represented, which is more than sufficient for many real-world applications. For longer abbreviations, the address space grows exponentially.</p><p>But the sheer number of possible combinations is only one aspect. How these shortcuts are generated is equally important. If the generation is random, it is essential to ensure that no duplicate codes are created – so-called collisions. These can be managed either by checking for their existence beforehand or by deterministic methods such as hash functions. However, hash methods are not without risks, especially under heavy load: The more entries there are, the higher the probability that two different URLs will receive the same short code, especially if the hash function has not been optimised for this use case.</p><p>Another criterion is the distribution of the generated shortcuts. A uniform distribution in the address space is desirable because, on the one hand, it reduces the risk of collisions, and on the other hand, it increases the efficiency of storage and retrieval mechanisms – for example, in sharding for distributed systems or caching in high-traffic environments. Cryptographically secure random numbers or specially designed generators play a crucial role here.</p><p>Overall, it can be said that the choice of alphabet, the length of the abbreviations and the way they are generated are not just technical parameters, but fundamental design decisions that significantly influence the security, efficiency and scalability of a URL shortener.</p><h2 id="3-architecture-of-a-url-shortener">3. Architecture of a URL shortener</h2><p>The architecture of a URL shortener is surprisingly compact at its core, but by no means trivial. Although its basic function is simply to link a long URL with a short alias, numerous technical and conceptual decisions arise in the details. These include data storage, the structure of API access, concurrency behaviour, and security against misuse. This chapter explains the central components and their interaction, deliberately avoiding external frameworks. Instead, the focus is on a modular, transparent structure in pure Java.</p><p>At the heart of the system is a mapping table – typically in the form of a map or a persistent key-value database – that uniquely assigns each generated short code to its corresponding original URL. This structure forms the backbone of the shortener. Crucially, this mapping must be both efficiently readable and consistently modifiable, especially under load or when accessed concurrently by multiple clients.</p><p>A typical URL shortener consists of three logically separate units: an input endpoint for registering a new URL, a redirection endpoint for evaluating a short link, and a management unit that provides metadata such as expiration times or access counters. In a purely Java-based solution without frameworks, network access is provided via the HTTP server introduced in Java 18.<strong>com.sun.net.httpserver</strong> package. This allows you to define REST-like endpoints with minimal overhead and to communicate with<strong>HttpExchange</strong> objects.</p><p>There are various options for storing mappings. In-memory structures, such as<strong>ConcurrentHashMap</strong> , offer maximum speed but are volatile and unsuitable for productive applications without a backup mechanism. Alternatively, file-based formats, relational databases, or object-oriented stores such as EclipseStore can be used. This paper will initially work with volatile storage to illustrate the basic logic. Persistence will be added modularly later.</p><p>Another key aspect concerns concurrency behaviour. Since URL shorteners are typically burdened by a large number of read accesses, for example, when calling short links, the architecture must be designed to allow concurrent access to the lookup table without locking conflicts. The same applies to the generation of new shortcuts, which must be atomic and collision-free. Java 24 introduces modern language tools, including virtual threads and structured concurrency, which can be utilised to manage server load in a more deterministic and scalable manner.</p><p>Last but not least, horizontal extensibility plays a role. A cleanly decoupled design allows the shortener to be easily transferred to distributed systems later. For example, the actual URL resolver can be operated as a stateless service, while data storage is outsourced to a shared backend. Caching strategies and load balancing can also be integrated much more easily in such a setup.</p><p>In summary, a URL shortener is much more than a simple string replacement. Its architecture must be both efficient, robust, and extensible—properties that can be easily achieved through a modular structure in pure Java.</p><h2 id="4-implementation-with-java-24">4. Implementation with Java 24</h2><h2 id="41-project-structure-and-module-overview">4.1 Project structure and module overview</h2><p>The implementation of the URL shortener follows a modular structure that supports both clarity in the source code and testability, as well as extensibility. The project is structured as a Java module and leverages the capabilities of the Java Platform Module System (JPMS). The goal is to separate the core functionality—that is, the management of URL mappings—from the network layer and persistence. This keeps the business logic independent of specific storage or transport mechanisms.</p><p>At the centre is a module called<strong>shortener.core</strong> , which contains all domain-specific classes: for example, the ShortUrlMapping, the UrlEncoder, as well as the central<strong>UrlMappingStore</strong> interface with a simple implementation in memory. A module<strong>shortener.http</strong> , which is based on Java&rsquo;s internal HTTP server. It implements the REST endpoints and utilises the core module&rsquo;s components for actual processing. Additional optional modules, such as those for persistence or analysis, can be added later.</p><p>To organise the code, a directory structure that clearly reflects the module and layer boundaries is recommended. Within the modules, a distinction should be made between<strong>api</strong> ,<strong>impl</strong> ,<strong>util</strong> and, if necessary,<strong>service</strong>.</p><h3 id="42-url-encoding-hashing-base62-and-alternatives">4.2 URL Encoding: Hashing, Base62 and Alternatives</h3><p>A central element of the shortener is the mechanism for generating short, unique codes. This implementation uses a hybrid method that generates a consecutive, atomic sequence number and converts it into a human-readable format using a Base62 encoder.</p><p>This choice has two advantages: First, it is deterministic and avoids collisions without the need for complex hash functions. Second, generated codes can be efficiently serialised and are easy to read, which is particularly relevant in marketing or print contexts. Alternatively, cryptographic hashes such as SHA-256 can be used when unpredictability and integrity protection are essential, for example, for signed links or zero-knowledge schemes.</p><p>The Base62 encoder is implemented as a pure utility class that encodes integer values ​​into a character string, where the alphabet consists of numbers and letters. Inverse decoding is also provided in case bidirectional analysis is required in the future.</p><h3 id="43-mapping-store-interface-implementation-synchronisation">4.3 Mapping Store: Interface, Implementation, Synchronisation</h3><p>For managing URL mappings, a clearly defined interface called<strong>UrlMappingStore</strong> provides methods for inserting new mappings, resolving short links, and optionally managing metadata. The default implementation, InMemoryUrlMappingStore, is based on a ConcurrentHashMap and utilises<strong>AtomicLong</strong> for sequence number generation.</p><p>This simple architecture is completely thread-safe and allows parallel access without external synchronisation mechanisms. The implementation can be replaced at any time with a persistent variant, for example, based on flat file storage or through integration with an object-oriented storage system such as EclipseStore.</p><p>This separation keeps the application core stable while treating storage as a replaceable detail—a classic example of the dependency inversion principle in the spirit of Clean Architecture.</p><h3 id="44-rest-api-with-pure-java-http-server-handler-routing">4.4 REST API with pure Java (HTTP server, handler, routing)</h3><p>The REST interface is implemented exclusively with the built-in tools of the JDK. Java provides the package<strong>com.sun.net.httpserver</strong> , which offers a minimalistic yet powerful HTTP server ideal for lean services. For the implementation of the API, a separate<strong>HttpHandler</strong> is defined that responds to specific routes, such as<strong>/shorten</strong> for POST requests and<strong>/{code}</strong> for forwarding.</p><p>The implementation is based on a clear separation between parsing, processing, and response generation. Incoming JSON messages are parsed manually or with the help of simple helper classes, without the need for external libraries. HTTP responses also follow a minimalist format, characterised by structured status codes, simple header management, and UTF-8-encoded bodies.</p><p>Routing is handled by a dispatcher class, which selects the appropriate handler based on the request path and HTTP method. Later extensions, such as CORS, OPTIONS handling, or versioning, are easily possible.</p><h3 id="45-error-handling-logging-and-monitoring">4.5 Error handling, logging and monitoring</h3><p>In a productive environment, robust error handling is essential. The implementation distinguishes between systematic errors (such as invalid inputs or missing short codes) and unexpected runtime errors (such as IO problems or race conditions). The former are reported with clear HTTP status codes, such as<strong>400 (Bad Request)</strong> or<strong>404 (Not Found).</strong> The latter leads to a generic<strong>500 Internal Server Erro</strong> r, with the causes being logged internally.</p><p>For logging, the JDK’s own<strong>java.util.logging</strong> This allows for platform-independent logging and can be replaced with SLF4J-compatible systems if needed. Monitoring metrics such as access counts, response times, or error statistics can be made accessible via a separate endpoint or JMX.</p><h2 id="5-security-aspects">5. Security aspects</h2><h3 id="51-abuse-opportunities-and-protection-mechanisms">5.1 Abuse opportunities and protection mechanisms</h3><p>A URL shortener can easily be used to obscure content. Attackers deliberately exploit the shortening to redirect recipients to phishing sites, malware hosts, or dubious content without the target address being immediately visible. This can pose significant risks, especially for automated distributions via social networks, chatbots, or email campaigns.</p><p>An adequate protection mechanism consists of automatically validating all target addresses upon insertion, for example, through syntactical URL checks, DNS resolution, and optionally through a background query (head request or proxy scan) that ensures that the target page is accessible and non-suspicious. Such checks should be modular so that they can be activated or deactivated depending on the environment (e.g., offline operation). Additionally, logging should be performed every time a short link is accessed, making it easier to identify patterns of abuse.</p><h3 id="52-rate-limiting-and-ip-based-throttling">5.2 Rate limiting and IP-based throttling</h3><p>Another risk lies in excessive use of the service, be it through botnets, targeted enumeration, or simple DoS behaviour. A robust URL shortener should therefore have rate limiting that restricts requests within a given time slot. This can be global, IP-based, or per-user, depending on the context.</p><p>In a Java implementation without frameworks, this can be achieved, for example, via a<strong>ConcurrentHashMap</strong> that maintains a timestamp or counter buffer for each IP address. If a threshold is exceeded, the connection is terminated with a status code of<strong>429 Too Many Requests</strong> rejected. This simple throttling can be supplemented with leaky bucket or token bucket algorithms if necessary to achieve a fairer distribution over time. For productive use, logging of critical threshold violations is also recommended.</p><h3 id="53-validity-period-and-deletion-concepts">5.3 Validity period and deletion concepts</h3><p>Not every short link should remain valid forever. A configurable validity period is essential, especially for security-critical applications, such as temporary document sharing or one-time authentication. A URL shortener should therefore offer the option of defining expiration times for each mapping.</p><p>On a technical level, it is sufficient to assign an expiration date to each mapping, which is checked during the lookup. When accessing expired short links, either an error status, such as<strong>410 Gone,</strong> is displayed, or the user is redirected to a defined information page. Additionally, there should be periodic cleanup mechanisms that remove expired or unused entries from memory, such as through a time-controlled cleanup process or lazy deletion upon access.</p><h3 id="54-protection-against-enumeration-and-information-leakage">5.4 Protection against enumeration and information leakage</h3><p>An often overlooked attack vector is the systematic scanning of the abbreviation space – for example, by automated retrieval of<strong>/aaaaaa</strong> until<strong>/zzzzzz.</strong> If a URL shortener delivers valid links without any protection mechanisms, potentially confidential information about the existence and use of links can be leaked.</p><p>An adequate protection consists in making the shortcuts themselves non-deterministic – for example, by using cryptographically generated, unpredictable tokens instead of continuous sequences. Additionally, access restrictions can be introduced, allowing only authenticated clients to access certain short links or excluding specific IP ranges. The targeted obfuscation of error responses – for example, by consistently issuing<strong>404 Not Found</strong> even with blocked or expired abbreviations – makes analysis more difficult for attackers.</p><p>A further risk arises when metadata such as creation time, number of accesses, or request origin is exposed unprotected via the API. Such information should only be accessible to authorised users or administrative interfaces and should never be part of the public API output.</p><h2 id="6-performance-and-optimisation">6. Performance and optimisation</h2><h3 id="61-access-times-and-hash-lookups">6.1 Access times and hash lookups</h3><p>The most common operation in a URL shortener is resolving a shortcode into its corresponding original URL. Since this is a classic lookup operation, the choice of the underlying data structure is crucial. In the standard implementation, a<strong>ConcurrentHashMap,</strong> which is optimised in Java 24, has fine-grained locking. This offers nearly constant access times – even under high concurrency – and is therefore ideal for read-intensive workloads, such as those typical of a shortener.</p><p>The latency of such an operation is in the range of a few microseconds, provided the lookup table is stored in main memory and no additional network or IO layers are involved. However, if data storage is outsourced to persistent systems, such as a relational database or a disk-based key-value store, the access time increases accordingly. Therefore, it is recommended to cache frequently accessed entries – either directly in memory or via a dedicated cache layer.</p><p>Performance also plays a role in the creation of new abbreviations. This is where sequence number generation using<strong>AtomicLong</strong> is used, providing a thread-safe, low-contention solution for linear ID assignment. Combined with Base62 encoding, this creates a fast, predictable, and collision-free process.</p><h3 id="62-memory-usage-and-garbage-collection">6.2 Memory usage and garbage collection</h3><p>Since a URL shortener must manage a growing number of entries over a longer period, it is worthwhile to examine its storage behaviour.<strong>ConcurrentHashMap.</strong> While this results in fast access times, it also means that all active mappings remain permanently in memory—unless cleanup is implemented. A simple mapping structure consisting of a shortcode, original URL, and an optional timestamp requires several hundred bytes per entry, depending on the JVM configuration and string length.</p><p>With several million entries, heap usage can reach several gigabytes. To improve efficiency, care should be taken to use objects sparingly. For example, common URL prefixes (e.g.<strong>https://</strong>) are replaced with symbolic constants. Records instead of classic POJOs also help reduce object size and minimise GC load.</p><p>In the long term, it is recommended to introduce an active or passive cleanup mechanism, such as TTL-based eviction or access counters, to specifically remove rarely used entries.<strong>WeakReference</strong> or soft caching should be considered with caution, since the semantics of such structures do not always lead to expected behaviour in the server context.</p><h3 id="63-benchmarking-local-tests-and-load-simulation">6.3 Benchmarking: Local tests and load simulation</h3><p>Systematic benchmarking is essential for objectively evaluating the performance of a URL shortener. At a local level, this can be achieved with simple Java benchmarks that measure sequence number generation, lookup time, and code distribution quality. Tools such as JMH (Java Microbenchmark Harness) can also be used. Although external tools are not used in this paper, a manual microbenchmarking approach using System.nanoTime and a targeted warm-up can provide valuable insights.</p><p>For more realistic tests, a load simulation with HTTP clients is suitable, for example, using simple JDK-based multi-thread scripts or tools such as<strong>curl</strong>. In particular, behaviour under high concurrent access load should be observed, both in terms of response times and resource consumption. Behaviour in the event of failed requests, rapid-fire access, or expired links should also be explicitly tested.</p><p>The goal of such benchmarks is not only to validate the maximum transaction rate, but also to verify stability under continuous load. A robust implementation should not only be high-performance but also deterministic in its response behaviour and resistant to out-of-memory errors. Optional profiling—for example, using JDK Flight Recorder—can reveal further optimisation potential.</p><h2 id="7-expansion-options-and-variants">7. Expansion options and variants</h2><h3 id="71-custom-aliases">7.1 Custom aliases</h3><p>A frequently expressed wish in practice is the ability to not only use automatically generated short links, but also to assign custom aliases – for example, for marketing campaigns, internal documents, or individual redirects. A custom alias, such as<strong>/travel2025</strong> is much easier to remember than a random Base62 token and can be integrated explicitly into communication and branding.</p><p>Technically speaking, this expands the mapping store&rsquo;s responsibility. Instead of only accepting numerically generated keys, the API must verify that a user-defined alias is syntactically valid, not already in use, and not reserved. A simple regex check, supplemented by a negative list for reserved terms (e.g.<strong>/admin</strong> ,<strong>/api</strong>), is sufficient to get started. This alias must then be treated equally to the automatically generated codes when stored.</p><p>This creates new failure modes, for example, when a user requests an alias that already exists. Such cases should be handled consistently with a<strong>409 Conflict.</strong> The API can optionally suggest alternative names—a small convenience feature with a significant impact on the user experience (UX).</p><h3 id="72-access-counting-and-analytics">7.2 Access counting and analytics</h3><p>A functional URL shortener is more than just a redirection tool—it&rsquo;s also an analytics tool. Tracking how often, when, and from where a short link was accessed is particularly relevant in the context of campaigns, product pages, or documented distribution.</p><p>To implement this functionality, each successful resolution of a short link must be saved as an event, ​​either by simply incrementing a counter or by fully logging with a timestamp, IP address, and user agent. For the in-memory variant, an additional<strong>AtomicLong</strong> or a metric structure aggregated via a map. Alternatively, detailed access data can be persisted in a dedicated log file or an external analytics module.</p><p>The evaluation can be performed either synchronously via API endpoints (e.g.,/stats/{alias}) or asynchronously via export formats such as JSON, CSV, or Prometheus metrics. Integration with existing logging systems (e.g. via<strong>java.util.logging</strong> or<strong>Logstash</strong>) is easily possible.</p><h3 id="73-qr-code-integration">7.3 QR-Code-Integration</h3><p>For physical media, such as posters, packaging, or invitations, displaying a short link as a QR code is a useful extension. Integrating QR code generation into the URL shortener enables the direct generation of a visually encoded image of the link from the API.</p><p>Since no external libraries are used, QR code generation can be performed using a compact Java-based algorithm, such as one based on bit matrix generation and SVG output. Alternatively, a Base64-encoded PNG file can be delivered via an endpoint URL such as<strong>/qr/{alias}.</strong> The underlying data structure remains unchanged – only the representation is extended.</p><p>This feature not only enhances practical utility but also expands the service&rsquo;s reach across multiple media channels.</p><h3 id="74-integration-into-messaging-or-tracking-systems">7.4 Integration into messaging or tracking systems</h3><p>In production architectures, a URL shortener typically operates in conjunction with other components. Instead, it is part of larger pipelines – for example, in email delivery, chatbots, content management systems, or user interaction tracking. Flexible integration with messaging systems such as Kafka, RabbitMQ, or simple webhooks allows every link creation or access to be transmitted as an event to external systems.</p><p>In a pure Java environment, this can be done via simple HTTP requests, log files, or asynchronous event queues. Scenarios are conceivable in which a notification is automatically sent to a third-party system for each new short link, for example, to generate personalised campaigns or for auditing purposes. Access to short links can also be mapped via events, which are subsequently statistically evaluated or visualised in dashboards.</p><p>Depending on the level of integration, it is recommended to implement a dedicated event dispatcher that encapsulates incoming or outgoing events and forwards them in a loosely coupled manner. This keeps the shortener itself lean and responsibilities clearly distributed.</p><h2 id="8-legal-and-ethical-aspects">8. Legal and ethical aspects</h2><h3 id="81-data-protection-for-link-tracking">8.1 Data protection for link tracking</h3><p>A URL shortener that logs visits automatically operates within the framework of data protection law. As soon as data such as IP addresses, timestamps, or user agents are stored, it is considered personal information in the legal sense, at least potentially. In the European Union, such data falls under the General Data Protection Regulation (GDPR), which entails specific obligations for operators.</p><p>The technical capability for analytics—for example, through access counting or geo-IP analysis—should therefore not be enabled implicitly. Instead, a URL shortener should be designed so that tracking mechanisms must be explicitly enabled, ideally with clear labelling for the end user. A differentiated configuration that distinguishes between anonymised and personal data collection is strongly recommended in professional environments.</p><p>Additionally, when storing personal data, a record of processing activities must be maintained, a legal basis (e.g., legitimate interest or consent) must be specified, and a defined retention period must be established. For publicly accessible shorteners, this may mean that tracking remains deactivated by default or is controlled via consent mechanisms. The implementation of such control structures is not part of the core functionality, but is an integral part of data protection-compliant operations.</p><h3 id="82-responsibility-for-forwarding">8.2 Responsibility for forwarding</h3><p>Another key point is the service provider&rsquo;s responsibility for the content to which the link is redirected. Even if a shortener technically only implements a redirect, legal responsibility arises as soon as the impression arises that the operator endorses or controls the target content. This is especially true for public or embedded shorteners, such as those found in corporate portals or social platforms.</p><p>The challenge lies in distinguishing between technical neutrality and de facto mediation. It is therefore advisable to integrate legal protection mechanisms into the architecture, for example, through a policy that excludes the upload of specific domains, regular URL revalidation, or the use of abuse detection systems. In the event of misuse or complaints, immediate deactivation of individual mappings should be possible, ideally via a separate administration interface.</p><p>This responsibility is not only legally relevant but also has a reputational impact: Shorteners used to spread harmful content quickly lose their credibility – and possibly also their access to platforms or search engines.</p><h3 id="83-transparency-and-disclosure-of-the-destination-address">8.3 Transparency and disclosure of the destination address</h3><p>A common criticism of URL shorteners is that the destination address is no longer visible to the user. This limits the ability to evaluate whether a link is trustworthy before clicking on it. From an ethical perspective, this raises the question of whether a shortener should offer a pre-check option.</p><p>Technically, this can be achieved through a special preview mode, such as via an appendage, by explicitly calling an API or HTML preview page that transparently resolves the mapping, for example, a link like<strong><a href="https://short.ly/abc123+">https://short.ly/abc123+</a>.</strong> Instead of redirecting immediately, the user first displays an information page that displays the original URL and redirects to the page if desired. This function can be supplemented with information about validity, access statistics, or trustworthiness.</p><p>A transparent approach to redirects not only increases user acceptance but also reduces the potential for abuse, especially among security-conscious target groups. In sensitive environments, a mandatory preview page – for example, for all non-authenticated users – can be a helpful measure.</p><h2 id="9-conclusion-and-outlook">9. Conclusion and outlook</h2><h3 id="91-lessons-learned">9.1 Lessons Learned</h3><p>The development of a URL shortener in pure Java, without frameworks or external libraries, has demonstrated how even seemingly trivial web services, upon closer inspection, reveal themselves to be complex systems with diverse requirements. From the basic function of address shortening to security aspects and operational and legal implications, the result is a system that must be architecturally well-structured, yet flexible and extensible.</p><p>The importance of a clear separation of responsibilities is particularly important: A stable mapping store, a deterministic encoder, a secure yet straightforward REST API, and understandable error handling form the backbone of a robust service. Modern language tools from Java 24, such as records, sealed types, and virtual threads, enable a remarkably compact, type-safe, and concurrency-capable implementation.</p><p>The conscious decision against frameworks not only maximised the learning effect but also contributed to a deeper understanding of HTTP, data storage, thread safety, and API design – a valuable perspective for developers who want to operate in a technology-independent environment.</p><h3 id="92-possible-further-developments-eg-blockchain-dnssec">9.2 Possible further developments (e.g. blockchain, DNSSEC)</h3><p>Despite their apparent simplicity, URL shorteners represent a fascinating field for technological innovation. There are efforts to move away from centralised management of the mapping between short code and target URL, instead using decentralised technologies such as blockchain. In this case, each link is stored as a transaction, providing resistance to manipulation and historical traceability. In practice, however, this places high demands on latency and infrastructure, which is why such approaches have been used so far rarely in production.</p><p>Another development strand lies in integration with DNSSEC-based procedures. This not only signs the shortcode itself, but also cryptographically verifies the authenticity of the resolved host. This could combine trust and verification, especially in security-critical areas such as government services, banks, or certificate authorities.</p><p>AI-supported heuristics, such as those for misuse detection or memory cleanup prioritisation, also offer potential. However, the integration of such mechanisms requires a data-efficient, explainable design that is compatible with applicable data protection regimes.</p><h3 id="93-importance-of-url-shorteners-in-the-context-of-digital-sovereignty">9.3 Importance of URL shorteners in the context of digital sovereignty</h3><p>In today&rsquo;s digital landscape, URL shorteners are more than just a convenience feature; they are a valuable tool. They influence the visibility, accessibility, and traceability of content. The question of whether and how a link is modified or redirected has a direct impact on information sovereignty and transparency, and thus on digital sovereignty.</p><p>Especially in the public sector, educational institutions, or organisations with strict compliance requirements, URL shorteners should not be operated as outsourced cloud services; instead, they should be developed in-house or at least integrated in a controlled manner. A self-hosted solution not only allows complete control over data flows and access histories but also protects against censorship-like outages or data-driven tracking by third parties.</p><p>This makes the URL shortener, as inconspicuous as its function may seem, a strategic component of a trustworthy IT infrastructure. It exemplifies the question: Who controls the path of information? In this respect, a custom shortener is not just a tool, but also a statement of identity.</p><p>The next part will be about the implementation itself..</p><p>Happy Coding</p>
]]></content:encoded><category>Java</category><category>Security</category><category>Uncategorized</category><media:content url="https://svenruppert.com/images/2025/06/ChatGPT-Image-10.-Juni-2025-21_51_48.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/06/ChatGPT-Image-10.-Juni-2025-21_51_48.png"/><enclosure url="https://svenruppert.com/images/2025/06/ChatGPT-Image-10.-Juni-2025-21_51_48.png" type="image/jpeg" length="0"/></item><item><title>If hashCode() lies and equals() is helpless</title><link>https://svenruppert.com/posts/if-hashcode-lies-and-equals-is-helpless/</link><pubDate>Fri, 06 Jun 2025 20:53:34 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/if-hashcode-lies-and-equals-is-helpless/</guid><description>A deep look into Java’s HashMap traps – visually demonstrated with Vaadin Flow.
The silent danger in the standard library The use of HashMap and HashSet is a common practice in everyday Java development. These data structures offer excellent performance for lookup and insert operations, as long as their fundamental assumptions are met. One of them is hashCode() of a key remains stable. But what if that&rsquo;s not the case?</description><content:encoded>&lt;![CDATA[<p>A deep look into Java’s HashMap traps – visually demonstrated with Vaadin Flow.</p><h3 id="the-silent-danger-in-the-standard-library">The silent danger in the standard library</h3><p>The use of<strong>HashMap</strong> and<strong>HashSet</strong> is a common practice in everyday Java development. These data structures offer excellent performance for lookup and insert operations, as long as their fundamental assumptions are met. One of them is<strong>hashCode()</strong> of a key remains stable. But what if that&rsquo;s not the case?</p><p>This is precisely where one of the most subtle and dangerous traps of the Java standard library lurks: mutable key objects. In this article, we not only demonstrate why this constellation is problematic but also illustrate the phenomenon interactively using Vaadin Flow. Readers will learn how the HashMap works internally, why<strong>equals()</strong> alone is not enough, and how to use modern language tools to generate robust, immutable keys.</p><ol><li><a href="https://svenruppert.com/2025/06/06/if-hashcode-lies-and-equals-is-helpless/#the-silent-danger-in-the-standard-library">The silent danger in the standard library</a></li><li><a href="https://svenruppert.com/2025/06/06/if-hashcode-lies-and-equals-is-helpless/#the-fundamental-problem-identity-hash-codes-and-lookup">The fundamental problem: identity, hash codes and lookup</a></li><li><a href="https://svenruppert.com/2025/06/06/if-hashcode-lies-and-equals-is-helpless/#the-classic-mistake-hashcode-depends-on-variable-attributes">The classic mistake hashCode() depends on variable attributes</a></li><li><a href="https://svenruppert.com/2025/06/06/if-hashcode-lies-and-equals-is-helpless/#security-critical-consequences-when-loss-of-consistency-becomes-an-attack-surface">Security-critical consequences: When loss of consistency becomes an attack surface</a></li><li><a href="https://svenruppert.com/2025/06/06/if-hashcode-lies-and-equals-is-helpless/#interactive-demo-with-vaadin-flow">Interactive Demo with Vaadin Flow</a></li><li><a href="https://svenruppert.com/2025/06/06/if-hashcode-lies-and-equals-is-helpless/#hashset">HashSet</a></li><li><a href="https://svenruppert.com/2025/06/06/if-hashcode-lies-and-equals-is-helpless/#strategies-to-avoid">Strategies to avoid</a></li><li><a href="https://svenruppert.com/2025/06/06/if-hashcode-lies-and-equals-is-helpless/#why-are-other-map-implementations-not-affected">Why are other map implementations not affected</a></li><li><a href="https://svenruppert.com/2025/06/06/if-hashcode-lies-and-equals-is-helpless/#conclusion-the-price-of-convenience">Conclusion: The price of convenience</a></li><li><a href="https://svenruppert.com/2025/06/06/if-hashcode-lies-and-equals-is-helpless/#demo-source-code-to-try-it-out-yourself">Demo source code to try it out yourself</a></li></ol><h3 id="the-fundamental-problem-identity-hash-codes-and-lookup">The fundamental problem: identity, hash codes and lookup</h3><p>Internally, each HashMap is an array of buckets, where the hash code of the key determines the position of an entry. The insertion process (<strong>put(K key, V value)</strong>) looks like this: First, the map calls<strong>key.hashCode()</strong> and calculates the index in the bucket array from this value, using internal spreading. If entries already exist at this position (e.g., due to hash collisions), a linear search is performed within the bucket or – if there are enough collisions – i.e., if there are more than eight entries in a bucket and the underlying array exceeds a specific size (default value: 64) – the bucket is internally transformed from a simple linked list into a balanced binary tree structure (more precisely: a red-black tree). This conversion improves the lookup performance from linear time O(n) to logarithmic time O(log n). The decision for these thresholds is based on the observation that collisions are rare in well-chosen hash functions, and the overhead of a tree structure is only worthwhile with a high entry density.<strong>equals()</strong> is used to identify the appropriate key or create a new entry.</p><p>When accessing (<strong>get(Object key)</strong>) the same process occurs: The hash code of the transferred key is calculated again, the corresponding bucket is determined and searched for a matching key via<strong>equals()</strong> However, if the hash code of the object differs from the original<strong>put()</strong> have changed, a different bucket is addressed – the original entry then remains invisible.</p><p>Removing (<strong>remove(Object key)</strong>) is subject to the same mechanisms: the map searches for the correct bucket via<strong>hashCode()</strong> and then compares the keys using<strong>equals()</strong>. Consistency of the hash-relevant data over the entire lifetime of the key is therefore essential. Only if this is guaranteed can the HashMap ensure its efficiency and correctness.</p><p>This means: Even access to the correct bucket depends exclusively on the result of the method<strong>hashCode()</strong> This value is calculated immediately upon access, combined with an internal transformation (such as bitwise shifting and XOR operations to achieve a more even distribution across the bucket array), and then used to index the bucket array. If the return value of<strong>hashCode().</strong> After inserting an object into the map, for example, by mutating an attribute that is included in the calculation, the newly calculated index points to a different bucket. However, the object being searched for does not exist there, which is why the map can no longer find the entry. This behaviour is not a malfunction of the HashMap, but the direct consequence of a breach of the fundamental contract<strong>hashCode()</strong> must remain consistent while maintaining the same internal object state. Violating this contract is abusing the HashMap in a way it was not designed for, with potentially serious consequences for data integrity and program logic.</p><h3 id="the-classic-mistake-hashcode-depends-on-variable-attributes">The classic mistake hashCode() depends on variable attributes</h3><p>Let&rsquo;s take the following example: An instance of the class<strong>Person</strong> with the values<strong>name = &ldquo;Alice&rdquo;</strong> and<strong>id = 42</strong> is created and stored as a key in a HashMap At the time of insertion, the map calculates the<strong>hashCode()</strong> based on the current state of the object – i.e.<strong>Objects.hash(&ldquo;Alice&rdquo;, 42)</strong> – and saves the entry in the corresponding bucket. After that, the field name of the object, e.g., is set to the value &ldquo;Bob&rdquo;. This changes the return value of<strong>hashCode()</strong> , for example,<strong>Objects.hash(&ldquo;Bob&rdquo;, 42)</strong> , which addresses a different bucket.</p><p>If you now try to access the map again with the same object,<strong>map.get(originalPerson)</strong> – the operation fails. The map calculates a new index from the current hash code, looks in the corresponding bucket, and doesn&rsquo;t find a matching entry. The original object is technically still in the map, but cannot be found.</p><p>The situation becomes even more misleading when one considers the<strong>entrySet()</strong> method**.** The entry is visible there, since the iteration is carried out directly via the internal chains, independent of the hash code. A comparison via<strong>equals()</strong> works with a newly created, identical object – after all,<strong>equals()</strong> is typically based on content equality and not on memory address or hash code.</p><p>This makes the HashMap de facto inconsistent: The element is still physically stored in the internal data array, but cannot be accessed via regular access paths, such as<strong>get(key),</strong> can no longer be found. To demonstrate this effect reproducibly, consider the following code example:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">Person</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">you</span><span class="w"/><span class="n">hand</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Person</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">id</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">id</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">id</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">equals</span><span class="p">(</span><span class="n">Object</span><span class="w"/><span class="n">o</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="n">o</span><span class="w"/><span class="k">instanceof</span><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">p</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">name</span><span class="p">,</span><span class="w"/><span class="n">p</span><span class="p">.</span><span class="na">name</span><span class="p">)</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">id</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">p</span><span class="p">.</span><span class="na">id</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="nf">hashCode</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">hash</span><span class="p">(</span><span class="n">name</span><span class="p">,</span><span class="w"/><span class="n">id</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MutableHashDemo</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Person</span><span class="w"/><span class="n">p</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Person</span><span class="p">(</span><span class="s">"Alice"</span><span class="p">,</span><span class="w"/><span class="n">42</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Map</span><span class="o">&lt;</span><span class="n">Person</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">map</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">map</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">p</span><span class="p">,</span><span class="w"/><span class="s">"Value"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Before change:"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"map.get(p): "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">map</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">p</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Mutation of the key</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">p</span><span class="p">.</span><span class="na">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Bob"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"After change:"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"map.get(p): "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">map</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">p</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Enthält key via entrySet: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">map</span><span class="p">.</span><span class="na">entrySet</span><span class="p">().</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="na">anyMatch</span><span class="p">(</span><span class="n">e</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getKey</span><span class="p">().</span><span class="na">equals</span><span class="p">(</span><span class="n">p</span><span class="p">)));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>The demo source code is available on github at</strong><a href="https://github.com/svenruppert/Blog---Core-Java---Mutable-HashMap-Keys-in-Java/blob/main/src/test/java/junit/com/svenruppert/MutableHashCodeDemoTest.java">https://github.com/svenruppert/Blog&mdash;Core-Java&mdash;Mutable-HashMap-Keys-in-Java/blob/main/src/test/java/junit/com/svenruppert/MutableHashCodeDemoTest.java</a></p><p>This example demonstrates that<strong>map.get(p)</strong> returns null after the change, although the<strong>entrySet()</strong> still contains the entry. The reason<strong>hashCode()</strong> returns a different value after the mutation so that the original bucket is no longer addressed.<strong>equals()</strong> alone does not help in this case, since the lookup fails due to the wrong index.</p><h3 id="security-critical-consequences-when-loss-of-consistency-becomes-an-attack-surface">Security-critical consequences: When loss of consistency becomes an attack surface</h3><p>While the loss of referentiality in a HashMap may appear at first glance to be merely a technical issue, closer inspection reveals security-relevant implications, particularly in systems that utilise state caching, authentication, or access control. If an object serves as a key in security-critical maps or sets – e.g., for detecting active sessions, auth tokens, or user permissions – and its hash code subsequently changes, a logical error condition arises. The system no longer &ldquo;sees&rdquo; the object, even though it still exists. This can lead to unintended access gaps or, worse still, unattended persistence of state.</p><p>A particularly dangerous scenario arises when mutable keys can be influenced from outside. One conceivable scenario is where an attacker can inject controlled values ​​into an object that then serves as a key. As soon as this key is changed—for example, through manipulated API usage or faulty deserialisation— it is removed from access control, even though it technically still exists. The result: logical access without valid authorisation.</p><p>In extreme cases, this can even lead to typical security-critical patterns, such as:</p><ul><li><p>Authorisation Bypass: An object is modified before the test, the<strong>contains()</strong> fails, and access is granted incorrectly.</p></li><li><p>Resource Lock Hijack: A lock object is created via<strong>a HashSet</strong> or<strong>Map</strong> managed by the system, but it can no longer be removed after mutation**–** a deadlock or race condition threatens.</p></li><li><p><strong>Denial of service due to hash collisions</strong> : If a system stores (or mutates) many mutable objects with controlled hash codes, hash collisions can be deliberately triggered, and performance problems can be created.</p></li></ul><p>Although a classic buffer overflow doesn&rsquo;t occur directly in Java due to the memory safety of the JVM model, the structural effect of an inconsistent HashMap is similar to an overflow at the logical level: An access lands in the &ldquo;wrong memory area&rdquo; (bucket), and the object is present but functionally invisible. This is a key point for security auditors and architects: Loss of consistency in hash-based structures can not only lead to incorrect behaviour but also become a potential entry point for complex attacks.</p><h3 id="interactive-demo-with-vaadin-flow">Interactive Demo with Vaadin Flow</h3><p>To make the described problem tangible, a minimalist demonstration application is recommended, e.g., with Vaadin Flow. The demo aims to allow the user to observe live how an object in a<strong>HashMap</strong> becomes “invisible” after a mutation.</p><p>The application consists of a simple Vaadin view with the following UI elements:</p><ul><li>Input fields for name and ID</li><li>A button to insert an object into a HashMap</li><li>A button to modify the name (and thus the hash code)</li><li>A button to execute map.get()</li><li>A button for iterating over entrySet()</li></ul><p>The view constructor first creates the graphical user interface. Two input fields – one for the name, one for the ID – are used to interact with the<strong>Person</strong> -Object.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="n">nameField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">(</span><span class="s">"Name"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="n">idField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">(</span><span class="s">"ID"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">TextArea</span><span class="w"/><span class="n">output</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextArea</span><span class="p">(</span><span class="s">"Output"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">mutableKey</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Person</span><span class="p">(</span><span class="s">"Alice"</span><span class="p">,</span><span class="w"/><span class="n">42</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">Person</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">map</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span></span></span></code></pre></div></div><p>Several buttons allow you to perform specific operations on the<strong>Map.</strong> The &ldquo;<strong>put(key, value)</strong> " button adds the current mutable Key-Object as key with the value &ldquo;<strong>Saved</strong> " to the map. This uses exactly the object whose name and id were determined at the beginning – here &ldquo;Alice&rdquo; and 42.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">putButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"put(key, value)"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">map</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">mutableKey</span><span class="p">,</span><span class="w"/><span class="s">"Saved"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>About the button<strong>&ldquo;Change name&rdquo;,</strong> the name of the mutableKey object at runtime. This leads to a state in which the contents of the object—and thus its hash code—change after it is inserted into the map.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">mutateButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Change name"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">mutableKey</span><span class="p">.</span><span class="na">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">nameField</span><span class="p">.</span><span class="na">getValue</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>The<strong>&ldquo;get(key)&rdquo;</strong> Button demonstrates this effect: The method<strong>map.get(mutableKey)</strong> attempts to retrieve the value using the (modified) object as a key.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">getButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"get(key)"</span><span class="p">,</span><span class="w"/><span class="n">and</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">map</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">mutableKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">output</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"Result of get(): "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">result</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>To illustrate this effect and, at the same time, provide an alternative access, the button<strong>&ldquo;search entrySet()&rdquo;</strong> has been added. It manually iterates through all key-value pairs of the map using the Stream API and compares the keys via<strong>equals()</strong> , regardless of the internal bucket structure. This means that an entry with the changed key object can still be found, provided<strong>equals()</strong> is correctly implemented and independent of the hashCode function.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">iterateButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"search entrySet()"</span><span class="p">,</span><span class="w"/><span class="n">and</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">String</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">map</span><span class="p">.</span><span class="na">entrySet</span><span class="p">().</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">entry</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">entry</span><span class="p">.</span><span class="na">getKey</span><span class="p">().</span><span class="na">equals</span><span class="p">(</span><span class="n">mutableKey</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">Map</span><span class="p">.</span><span class="na">Entry</span><span class="p">::</span><span class="n">getValue</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">findFirst</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">orElse</span><span class="p">(</span><span class="s">"Not found via equals()"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">output</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"entrySet(): "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">result</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">//Here is the complete source code of the view.</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">PATH</span><span class="p">,</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainLayout</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">VersionOneView</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">PATH</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"versionone"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="n">nameField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">(</span><span class="s">"Name"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="n">idField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">(</span><span class="s">"ID"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">TextArea</span><span class="w"/><span class="n">output</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextArea</span><span class="p">(</span><span class="s">"Output"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">mutableKey</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Person</span><span class="p">(</span><span class="s">"Alice"</span><span class="p">,</span><span class="w"/><span class="n">42</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Map</span><span class="o">&lt;</span><span class="n">Person</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="n">map</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HashMap</span><span class="o">&lt;&gt;</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="nf">VersionOneView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Initializing VersionOneView"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">nameField</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="n">mutableKey</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">idField</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="n">String</span><span class="p">.</span><span class="na">valueOf</span><span class="p">(</span><span class="n">mutableKey</span><span class="p">.</span><span class="na">getId</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">output</span><span class="p">.</span><span class="na">setWidth</span><span class="p">(</span><span class="s">"600px"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Button</span><span class="w"/><span class="n">putButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"put(key, value)"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Putting value into map with key: {}"</span><span class="p">,</span><span class="w"/><span class="n">mutableKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">map</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">mutableKey</span><span class="p">,</span><span class="w"/><span class="s">"Saved"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">output</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"Inserted: "</span><span class="o">+</span><span class="w"/><span class="n">mutableKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Button</span><span class="w"/><span class="n">mutateButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Change name"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Changing name from {} to {}"</span><span class="p">,</span><span class="w"/><span class="n">mutableKey</span><span class="p">.</span><span class="na">getName</span><span class="p">(),</span><span class="w"/><span class="n">nameField</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">mutableKey</span><span class="p">.</span><span class="na">setName</span><span class="p">(</span><span class="n">nameField</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">output</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"Name changed to: "</span><span class="o">+</span><span class="w"/><span class="n">mutableKey</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Button</span><span class="w"/><span class="n">getButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"get(key)"</span><span class="p">,</span><span class="w"/><span class="n">and</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Getting value for key: {}"</span><span class="p">,</span><span class="w"/><span class="n">mutableKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">String</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">map</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">mutableKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Get result: {}"</span><span class="p">,</span><span class="w"/><span class="n">result</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">output</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"Result of get(): "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">result</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">Button</span><span class="w"/><span class="n">iterateButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"search entrySet()"</span><span class="p">,</span><span class="w"/><span class="n">and</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Searching through entrySet for key: {}"</span><span class="p">,</span><span class="w"/><span class="n">mutableKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">String</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">map</span><span class="p">.</span><span class="na">entrySet</span><span class="p">().</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">entry</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">entry</span><span class="p">.</span><span class="na">getKey</span><span class="p">().</span><span class="na">equals</span><span class="p">(</span><span class="n">mutableKey</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">Map</span><span class="p">.</span><span class="na">Entry</span><span class="p">::</span><span class="n">getValue</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">findFirst</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">.</span><span class="na">orElse</span><span class="p">(</span><span class="s">"Not found via equals()"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"EntrySet search result: {}"</span><span class="p">,</span><span class="w"/><span class="n">result</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">output</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"entrySet(): "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">result</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">add</span><span class="p">(</span><span class="n">nameField</span><span class="p">,</span><span class="w"/><span class="n">idField</span><span class="p">,</span><span class="w"/><span class="n">putButton</span><span class="p">,</span><span class="w"/><span class="n">mutateButton</span><span class="p">,</span><span class="w"/><span class="n">getButton</span><span class="p">,</span><span class="w"/><span class="n">iterateButton</span><span class="p">,</span><span class="w"/><span class="n">output</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"VersionOneView initialization completed"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Person</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kt">int</span><span class="w"/><span class="n">id</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="nf">Person</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">id</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">this</span><span class="p">.</span><span class="na">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">this</span><span class="p">.</span><span class="na">id</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">id</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">//SNIP getter setter</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">equals</span><span class="p">(</span><span class="n">Object</span><span class="w"/><span class="n">o</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="n">theinstanceof</span><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">p</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">&amp;&amp;</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">name</span><span class="p">,</span><span class="w"/><span class="n">p</span><span class="p">.</span><span class="na">name</span><span class="p">)</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="o">&amp;&amp;</span><span class="w"/><span class="n">id</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">p</span><span class="p">.</span><span class="na">id</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="nf">hashCode</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">hash</span><span class="p">(</span><span class="n">name</span><span class="p">,</span><span class="w"/><span class="n">id</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="k">return</span><span class="w"/><span class="n">name</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" ("</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">id</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">")"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span></span></span></code></pre></div></div><p>This concise yet powerful view enables any reader to directly observe the effects of changing keys.</p><p><strong>The demo source code is available on github at</strong><a href="https://github.com/svenruppert/Blog---Core-Java---Mutable-HashMap-Keys-in-Java/blob/main/src/main/java/com/svenruppert/flow/views/version01/VersionOneView.java">https://github.com/svenruppert/Blog&mdash;Core-Java&mdash;Mutable-HashMap-Keys-in-Java/blob/main/src/main/java/com/svenruppert/flow/views/version01/VersionOneView.java</a>** **</p><p>Now, let&rsquo;s modify the view slightly and display all entries from the EntrySet. The hash codes are compared, and the result is output.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Button</span><span class="w"/><span class="n">iterateButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"search entrySet()"</span><span class="p">,</span><span class="w"/><span class="n">_</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"Searching through entrySet for key: {}"</span><span class="p">,</span><span class="w"/><span class="n">mutableKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">StringBuilder</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">map</span><span class="p">.</span><span class="na">forEach</span><span class="p">((</span><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">value</span><span class="p">)</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kt">boolean</span><span class="w"/><span class="n">isMatch</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">key</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">mutableKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">result</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">"""</span></span></span><span class="line"><span class="cl"><span class="s">                                   Key: %s, Value: %s, HashCode %s Match: %s"""</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                               </span><span class="n">key</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                               </span><span class="n">value</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                               </span><span class="n">key</span><span class="p">.</span><span class="na">hashCode</span><span class="p">(),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                               </span><span class="n">isMatch</span><span class="w"/><span class="o">?</span><span class="w"/><span class="s">"And"</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"No"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">result</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="s">"\n"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">logger</span><span class="p">().</span><span class="na">info</span><span class="p">(</span><span class="s">"EntrySet search result: {}"</span><span class="p">,</span><span class="w"/><span class="n">result</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="n">output</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"entrySet():\n"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">result</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">()</span><span class="w"/><span class="o">?</span><span class="w"/><span class="n">result</span><span class="p">.</span><span class="na">toString</span><span class="p">()</span><span class="w"/><span class="p">:</span><span class="w"/><span class="s">"Map is empty"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span></span></span></code></pre></div></div><p>Now you can clearly see how an instance now exists multiple times in the same hash map. If you think about it, you can think of some very unpleasant applications that could even be used to specifically attack a system. But more on that in another blog post.</p><h3 id="hashset">HashSet</h3><p>Since a<strong>HashSet</strong> internally is nothing other than a<strong>HashMap</strong> , where the keys are the actual set elements and the values ​​are a constant dummy (such as<strong>PRESENT = new Object()</strong>), all the problems described here apply in the same way. If an object is modified after being inserted into the set, and this change affects one of the attributes that is used in the calculation of<strong>hashCode()</strong> , the object is searched in the wrong bucket. The method<strong>contains()</strong> returns<strong>false,</strong> even though the object is stored in the set.<strong>remove()</strong> fails because the same logic as get() takes effect: the<strong>HashMap</strong> finds the bucket using the current hash code and does not recognise the key there.</p><p>This behaviour is particularly critical when<strong>HashSet</strong> is used to temporarily store security-relevant information, for example, to manage already authenticated users, valid tokens, or temporary permissions. An unintentionally mutated key can lead to access being incorrectly denied or even security checks being bypassed—a classic case of a logical security vulnerability that is barely noticeable and difficult to debug.</p><p>In safety-critical modules, set elements should therefore always be immutable and defensively constructed. This means, in concrete terms, no changeable fields, no public modifiability, and – ideally – construction via a<strong>record</strong> or builder with final attributes.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Set<span class="nt">&lt;Person&gt;</span> people = new HashSet<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">people.add(p); // p.hashCode() ist X</span></span><span class="line"><span class="cl">p.name = "Malicious";</span></span><span class="line"><span class="cl">System.out.println(people.contains(p)); // false</span></span></code></pre></div></div><h3 id="strategies-to-avoid">Strategies to avoid</h3><p>The simplest and most effective strategy is to use only immutable objects as keys. Since Java 16,<strong>records</strong> are the idiomatically correct tool for this. They automatically generate equals() and hashCode() based on final fields.</p><p><strong>record PersonKey(String name, int id) {}</strong></p><p>Alternatively, if mutable state is unavoidable, the object<strong>before</strong> A modification can remove the hash code from the map and then reinsert it with an updated hash code. This is a dangerous workaround that is rarely implemented correctly in practice.</p><h3 id="why-are-other-map-implementations-not-affected">Why are other map implementations not affected</h3><p>The problem described here occurs specifically in<strong>HashMap</strong> and structures based on it, such as<strong>HashSet,</strong> because a calculated hash code determines the access path. Other map implementations in the JDK – such as<strong>TreeMap</strong> ,<strong>LinkedHashMap</strong> , or<strong>EnumMap</strong> - are not affected in this respect or only to a lesser extent.</p><p>The<strong>TreeMap,</strong> for example, is not based on hash code-based indexing, but on a sorted tree structure (red-black tree). It uses either the natural order of the keys (via Comparable) or a provided Comparator. This means that access does not depend on the result of<strong>hashCode()</strong> but from the stable comparability through<strong>compareTo()</strong> or<strong>compare(K1, K2).</strong> A change to an attribute that is included in the comparison logic can also lead to inconsistent behaviour, for example,<strong>get()</strong> or<strong>remove()</strong>. However, the mechanism is more transparent and controllable because sorting is done using a clearly defined comparison function.</p><p>Also,<strong>LinkedHashMap</strong> , although internally a<strong>HashMap</strong> , is not immune to the mutable hashcode problem because it uses the same bucket logic. However, it also provides a deterministic iteration order, which can aid in debugging, but does not alter the underlying problem.</p><p>The<strong>EnumMap.</strong> Finally, it is protected against this problem by design, as it is exclusively of enum types as keys. These are immutable by language definition and have stable, final hash codes. Thus, mutation is impossible, and the map remains robust against this class of errors.</p><p>You should therefore carefully consider which map implementation is used in each context, and whether a stable key state can be guaranteed or whether alternative ordering mechanisms should be used.</p><h3 id="conclusion-the-price-of-convenience">Conclusion: The price of convenience</h3><p>In daily development, the problem is often overlooked because it only manifests itself in specific situations – for example, after multiple operations, in multi-threading, or integrations across API boundaries. However, the consequences are severe: lost entries, unexplained<strong>null values</strong> , and inconsistent state.</p><p>Anyone who wants to use<strong>HashMaps</strong> efficiently and correctly should respect their internal rules, particularly ensuring that key objects do not change subsequently.</p><h3 id="demo-source-code-to-try-it-out-yourself">Demo source code to try it out yourself</h3><p>The complete source code for the demo view with Vaadin Flow is publicly available on GitHub. The application can be run efficiently locally using&rsquo; mvn jetty: run&rsquo;. The web application will then be available at the address<a href="http://localhost:8080/">http://localhost:8080/</a>.</p><p>GitHub:<a href="https://github.com/dein-projekt/mutable-hashcode-demo"/><a href="https://github.com/svenruppert/Blog---Core-Java---Mutable-HashMap-Keys-in-Java">https://github.com/svenruppert/Blog---Core-Java---Mutable-HashMap-Keys-in-Java</a></p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Uncategorized</category><media:content url="https://svenruppert.com/images/2025/06/ChatGPT-Image-6.-Juni-2025-20_48_59.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/06/ChatGPT-Image-6.-Juni-2025-20_48_59.png"/><enclosure url="https://svenruppert.com/images/2025/06/ChatGPT-Image-6.-Juni-2025-20_48_59.png" type="image/jpeg" length="0"/></item><item><title>Creating a simple file upload/download application with Vaadin Flow</title><link>https://svenruppert.com/posts/creating-a-simple-file-upload-download-application-with-vaadin-flow/</link><pubDate>Tue, 20 May 2025 17:34:15 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/creating-a-simple-file-upload-download-application-with-vaadin-flow/</guid><description>Vaadin Flow is a robust framework for building modern web applications in Java, where all UI logic is implemented on the server side. In this blog post, we&rsquo;ll make a simple file management application step by step that allows users to upload files, save them to the server, and download them again when needed. This is a great way to demonstrate how to build protection against CWE-22, CWE-377, and CWE-778 step by step.</description><content:encoded>&lt;![CDATA[<p>Vaadin Flow is a robust framework for building modern web applications in Java, where all UI logic is implemented on the server side. In this blog post, we&rsquo;ll make a simple file management application step by step that allows users to upload files, save them to the server, and download them again when needed. This is a great way to demonstrate how to build protection against CWE-22, CWE-377, and CWE-778 step by step.</p><ol><li><a href="https://svenruppert.com/2025/05/20/creating-a-simple-file-upload-download-application-with-vaadin-flow/#basic-project-structure">Basic project structure</a></li><li><a href="https://svenruppert.com/2025/05/20/creating-a-simple-file-upload-download-application-with-vaadin-flow/#add-file-upload-functionality">Add file upload functionality.</a></li><li><a href="https://svenruppert.com/2025/05/20/creating-a-simple-file-upload-download-application-with-vaadin-flow/#add-download-functionality">Add download functionality</a></li><li><a href="https://svenruppert.com/2025/05/20/creating-a-simple-file-upload-download-application-with-vaadin-flow/#best-practices-uses-java-nio">Best Practices uses Java NIO.</a></li><li><a href="https://svenruppert.com/2025/05/20/creating-a-simple-file-upload-download-application-with-vaadin-flow/#cwe-22-path-traversal-and-protection-measures">CWE-22: Path Traversal and Protection Measures</a></li><li><a href="https://svenruppert.com/2025/05/20/creating-a-simple-file-upload-download-application-with-vaadin-flow/#cwe-377-insecure-temporary-files">CWE-377: Insecure temporary files</a></li><li><a href="https://svenruppert.com/2025/05/20/creating-a-simple-file-upload-download-application-with-vaadin-flow/#cwe-778-insufficient-logging">CWE-778: Insufficient logging</a></li><li><a href="https://svenruppert.com/2025/05/20/creating-a-simple-file-upload-download-application-with-vaadin-flow/#summary">Summary</a></li></ol><p>In this example, we&rsquo;re focusing exclusively on functionality rather than on graphic design. The latter has been intentionally kept very simple to focus on the technical aspects.</p><p><strong><em>The source texts for this article can be found at:</em></strong><a href="https://github.com/Java-Publications/Blog---Secure-Coding-Practices---CWE-022--377--778---A-practical-Demo"><em>https://github.com/Java-Publications/Blog&mdash;Secure-Coding-Practices&mdash;CWE-022&ndash;377&ndash;778&mdash;A-practical-Demo</em></a>** __**</p><h3 id="basic-project-structure">Basic project structure</h3><p>To begin, we will create a new Vaadin project. The easiest way to do this is via the project starter, which you can find at<a href="https://start.vaadin.com/">https://start.vaadin.com/</a> or by using an existing Maven template. The file structure of our project essentially looks like this:</p><p><figure><img src="/images/2025/05/ProjectStrukture.jpg" alt="" loading="lazy" decoding="async"/>
Screenshot</p><p>The file<code>**MainView.java**</code> will be the application&rsquo;s central entry point. Here, we will implement the user interface and the logic for file uploads and downloads.</p><h3 id="add-file-upload-functionality">Add file upload functionality.</h3><p>First, we will create a simple user interface that allows users to upload files. In<code>**MainView.java**</code>, our basic setup looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.svenruppert.filemanager</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.button.Button</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.notification.Notification</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.orderedlayout.VerticalLayout</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.upload.Upload</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.upload.receivers.MemoryBuffer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.router.Route</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.FileOutputStream</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.InputStream</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Route</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">MainView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">MemoryBuffer</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">MemoryBuffer</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Upload</span><span class="w"/><span class="n">upload</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Upload</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">setMaxFiles</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">addSucceededListener</span><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">event</span><span class="p">.</span><span class="na">getFileName</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">inputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">buffer</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">File</span><span class="w"/><span class="n">targetFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"uploads/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">targetFile</span><span class="p">.</span><span class="na">getParentFile</span><span class="p">().</span><span class="na">mkdirs</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">FileOutputStream</span><span class="w"/><span class="n">outputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileOutputStream</span><span class="p">(</span><span class="n">targetFile</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">inputStream</span><span class="p">.</span><span class="na">transferTo</span><span class="p">(</span><span class="n">outputStream</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"File "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" uploaded successfully!"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Error uploading file"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">add</span><span class="p">(</span><span class="n">upload</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this code, we use<code>MemoryBuffer</code> to temporarily save the uploaded file and then write it to the<code>uploads/</code> directory. If the target directory doesn&rsquo;t already exist, it will be created automatically. Using<code>**MemoryBuffer**</code> allows easy and secure file management before it is written to the hard disk.</p><h3 id="add-download-functionality">Add download functionality</h3><p>To list the downloadable files, we&rsquo;ll add a button that allows users to download any file from the directory. This improves the user experience and ensures users can access their uploaded files easily. Here we&rsquo;ll extend the user interface:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.grid.Grid</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.html.Anchor</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">MainView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Upload function as described above</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">MemoryBuffer</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">MemoryBuffer</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Upload</span><span class="w"/><span class="n">upload</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Upload</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">setMaxFiles</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">addSucceededListener</span><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">event</span><span class="p">.</span><span class="na">getFileName</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">inputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">buffer</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">File</span><span class="w"/><span class="n">targetFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"uploads/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">targetFile</span><span class="p">.</span><span class="na">getParentFile</span><span class="p">().</span><span class="na">mkdirs</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">FileOutputStream</span><span class="w"/><span class="n">outputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileOutputStream</span><span class="p">(</span><span class="n">targetFile</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">inputStream</span><span class="p">.</span><span class="na">transferTo</span><span class="p">(</span><span class="n">outputStream</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"File "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" uploaded successfully!"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">updateFileList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Error uploading file"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">add</span><span class="p">(</span><span class="n">upload</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">updateFileList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">updateFileList</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">removeAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">File</span><span class="w"/><span class="n">folder</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"uploads"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">File</span><span class="o">[]</span><span class="w"/><span class="n">listOfFiles</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">folder</span><span class="p">.</span><span class="na">listFiles</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">listOfFiles</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">File</span><span class="w"/><span class="n">file</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">listOfFiles</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">file</span><span class="p">.</span><span class="na">isFile</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">Anchor</span><span class="w"/><span class="n">downloadLink</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Anchor</span><span class="p">(</span><span class="s">"/uploads/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">file</span><span class="p">.</span><span class="na">getName</span><span class="p">(),</span><span class="w"/><span class="n">file</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">downloadLink</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"download"</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">add</span><span class="p">(</span><span class="n">downloadLink</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Using the method<code>**updateFileList()**</code> displays the files stored in the<code>uploads/</code> directory as a list, and creates an<code>anchor</code> element for each file, which serves as a download link. This makes the interface more intuitive and allows users to manage uploaded files easily.</p><h3 id="best-practices-uses-java-nio">Best Practices uses Java NIO.</h3><p>We can use Java NIO (New Input/Output) instead of traditional IO streams to improve efficiency and security when handling files. Java NIO provides non-blocking IO operations, enabling better performance and scalability. It also supports more flexible and secure file system operations.</p><p>We adapt our code to use Java NIO classes like<code>**Files**</code> and<code>**Path**</code> to save the uploaded files. Here&rsquo;s an improved version of the upload code that uses Java NIO:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Files</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Path</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Paths</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.StandardCopyOption</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">MainView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">MemoryBuffer</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">MemoryBuffer</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Upload</span><span class="w"/><span class="n">upload</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Upload</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">setMaxFiles</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">addSucceededListener</span><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">event</span><span class="p">.</span><span class="na">getFileName</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Path</span><span class="w"/><span class="n">targetPath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"uploads"</span><span class="p">).</span><span class="na">resolve</span><span class="p">(</span><span class="n">fileName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">inputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">buffer</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Files</span><span class="p">.</span><span class="na">createDirectories</span><span class="p">(</span><span class="n">targetPath</span><span class="p">.</span><span class="na">getParent</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Files</span><span class="p">.</span><span class="na">copy</span><span class="p">(</span><span class="n">inputStream</span><span class="p">,</span><span class="w"/><span class="n">targetPath</span><span class="p">,</span><span class="w"/><span class="n">StandardCopyOption</span><span class="p">.</span><span class="na">REPLACE_EXISTING</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"File "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" uploaded successfully!"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">updateFileList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Error uploading file"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">add</span><span class="p">(</span><span class="n">upload</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">updateFileList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This version uses the<code>Files</code> and<code>Paths</code> classes to create directories and store files. This improves code readability and maintainability and leverages the advantages of the NIO classes, such as better exception handling and more flexible path operations. Using Java NIO makes file operations more efficient and secure, especially when working with multiple threads concurrently.</p><h3 id="cwe-22-path-traversal-and-protection-measures">CWE-22: Path Traversal and Protection Measures</h3><p>CWE-22, also known as &ldquo;Path Traversal,&rdquo; is a vulnerability that occurs when users can access unauthorised files and directories in the file system via insecure path names. This is typically done by users inserting special character strings such as<code>../</code> into filenames to extend beyond the boundaries of the permitted directory. If this is not controlled correctly, an attacker could gain access to critical system files, potentially leading to a serious security compromise.</p><p>In our file management application, path traversal is risky if the filename is used directly and without verification to save or retrieve a file in the file system. Attackers could attempt to manipulate path information and overwrite or read files outside the intended directory.</p><p>To avoid vulnerabilities like CWE-22 (Path Traversal) in your application, you should be especially careful with user-supplied filenames. Attackers could attempt to access files outside their intended location using manipulated path names—for example, by entering something like../../etc/passwd. Therefore, checking and cleaning all paths and file names is essential before using them. Here are a few best practices you should implement:</p><ul><li><p><strong>Path cleanup:</strong> Use methods like Path.normalize()from the Java NIO package to clean paths. This automatically removes dangerous constructs such as double slashes or relative elements (..) that attackers could use to traverse directories.</p></li><li><p><strong>Directory restriction:</strong> Make sure that the path you use to save files is within a secure, predefined home directory – e.g. uploads/ You can verify this by comparing the cleaned destination path with the expected base directory. You should allow the upload only if the destination path is truly within the allowed range.</p></li><li><p><strong>File name validation:</strong> It&rsquo;s not enough to check the path alone. The filename itself can also contain dangerous characters. It&rsquo;s best to allow only simple, non-critical characters, such as letters, numbers, hyphens, and periods. Using a regular expression like[a-za-Z0-9._—], you can specifically remove or replace everything else.</p></li></ul><p>Considering these points makes it significantly more difficult for attackers to control paths and protect your server structure and users&rsquo; sensitive data.</p><p>Here is a customised version of our application that implements protections against CWE-22:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Files</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Path</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Paths</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.StandardCopyOption</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">MainView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">MemoryBuffer</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">MemoryBuffer</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Upload</span><span class="w"/><span class="n">upload</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Upload</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">setMaxFiles</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">addSucceededListener</span><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">sanitizeFileName</span><span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="na">getFileName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Path</span><span class="w"/><span class="n">targetPath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"uploads"</span><span class="p">).</span><span class="na">resolve</span><span class="p">(</span><span class="n">fileName</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Prevent the path from being outside the upload directory</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">targetPath</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"uploads"</span><span class="p">).</span><span class="na">toAbsolutePath</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Invalid file path! Upload aborted."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">inputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">buffer</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Files</span><span class="p">.</span><span class="na">createDirectories</span><span class="p">(</span><span class="n">targetPath</span><span class="p">.</span><span class="na">getParent</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Files</span><span class="p">.</span><span class="na">copy</span><span class="p">(</span><span class="n">inputStream</span><span class="p">,</span><span class="w"/><span class="n">targetPath</span><span class="p">,</span><span class="w"/><span class="n">StandardCopyOption</span><span class="p">.</span><span class="na">REPLACE_EXISTING</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"File "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" uploaded successfully!"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">updateFileList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Error uploading file"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">add</span><span class="p">(</span><span class="n">upload</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">updateFileList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">sanitizeFileName</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="n">fileName</span><span class="p">.</span><span class="na">replaceAll</span><span class="p">(</span><span class="s">"[^a-zA-Z0-9._-]"</span><span class="p">,</span><span class="w"/><span class="s">"_"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this updated version of the application, we&rsquo;ve added a<code>sanitizeFileName()</code> method to ensure the filename doesn&rsquo;t contain any dangerous characters. We also normalise the path with<code>Path.normalize()</code> and verify that the final path is within the desired<code>uploads</code> directory. If the path is outside this directory, the upload is aborted and an appropriate error message is displayed.</p><p>These changes ensure that attackers cannot gain unauthorised access to the file system by manipulating file names. This keeps the application secure and protects both the server and the data stored on it from misuse.</p><h3 id="cwe-377-insecure-temporary-files">CWE-377: Insecure temporary files</h3><p>CWE-377, also known as &ldquo;Insecure Temporary File,&rdquo; describes a security vulnerability that occurs when you create temporary files in a way that can be exploited by attackers. Such files are often used to cache content, either during processing or for temporary storage in general. However, if you create them insecurely, attackers could access, tamper with, or even overwrite them.</p><p>A typical attack scenario: An attacker creates a symbolic link (symlink) pointing to a critical system file – your application then unknowingly writes data there. Or they manipulate the file while it&rsquo;s still being processed, thereby introducing unwanted content or malicious code into the system. The consequences can range from data loss and integrity violations to a complete system compromise.</p><p>Temporary files are also created in your file management application—for example, when processing uploads. Therefore, you must create these files securely. Here are a few best practices to effectively avoid CWE-377:</p><ul><li><p><strong>Safe methods for creating temporary files:</strong> In Java, use the Files.createTempFile() method. It automatically creates a temporary file with a unique, random name, reducing the risk of attackers accessing it.</p></li><li><p><strong>Restrict access rights:</strong> Only your process can access the temporary file. Set the file permissions so no other users or services have access—this is especially important in shared environments.</p></li><li><p><strong>Use unpredictable file names:</strong> Avoid using fixed or easily guessed names for temporary files. Otherwise, attackers could create a file with the same name beforehand or overwrite existing ones.</p></li></ul><p>Considering these points, you can securely handle temporary files and protect your application from an often underestimated attack vector.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Files</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Path</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Paths</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.StandardCopyOption</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">MainView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">MemoryBuffer</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">MemoryBuffer</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Upload</span><span class="w"/><span class="n">upload</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Upload</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">setMaxFiles</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">addSucceededListener</span><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">sanitizeFileName</span><span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="na">getFileName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Path</span><span class="w"/><span class="n">targetPath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"uploads"</span><span class="p">).</span><span class="na">resolve</span><span class="p">(</span><span class="n">fileName</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Prevent the path from being outside the upload directory</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">targetPath</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"uploads"</span><span class="p">).</span><span class="na">toAbsolutePath</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Invalid file path! Upload aborted."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">inputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">buffer</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Create a secure temporary file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Path</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">createTempFile</span><span class="p">(</span><span class="s">"upload_"</span><span class="p">,</span><span class="w"/><span class="s">"tmp"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Files</span><span class="p">.</span><span class="na">copy</span><span class="p">(</span><span class="n">inputStream</span><span class="p">,</span><span class="w"/><span class="n">tempFile</span><span class="p">,</span><span class="w"/><span class="n">StandardCopyOption</span><span class="p">.</span><span class="na">REPLACE_EXISTING</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Move the temporary file to the target directory</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Files</span><span class="p">.</span><span class="na">createDirectories</span><span class="p">(</span><span class="n">targetPath</span><span class="p">.</span><span class="na">getParent</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Files</span><span class="p">.</span><span class="na">move</span><span class="p">(</span><span class="n">tempFile</span><span class="p">,</span><span class="w"/><span class="n">targetPath</span><span class="p">,</span><span class="w"/><span class="n">StandardCopyOption</span><span class="p">.</span><span class="na">REPLACE_EXISTING</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"File "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" uploaded successfully!"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">updateFileList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Error uploading file"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">add</span><span class="p">(</span><span class="n">upload</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">updateFileList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">sanitizeFileName</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="n">fileName</span><span class="p">.</span><span class="na">replaceAll</span><span class="p">(</span><span class="s">"[^a-zA-Z0-9._-]"</span><span class="p">,</span><span class="w"/><span class="s">"_"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this revised version, you create a secure temporary file with Files.createTempFile(). You use this to temporarily store the uploaded content before moving it to the final destination directory, ensuring that third parties cannot tamper with the temporary file.</p><p>Only after the content has been successfully saved do you move the file to the correct location in the file system. This way, you can upload the files in a controlled and secure manner and minimise the risk of temporary files becoming a gateway for attackers.</p><h3 id="cwe-778-insufficient-logging">CWE-778: Insufficient logging</h3><p>CWE-778, also known as &ldquo;Insufficient Logging,&rdquo; describes a security vulnerability that occurs when your application doesn&rsquo;t log enough to detect and track security-relevant events. If you don&rsquo;t maintain detailed logging, attempted attacks, unauthorised access, or system errors may go unnoticed for a long time, or you may not have enough information to respond appropriately in an emergency.</p><p>Especially in safety-critical applications, you should document all critical actions and errors to understand what happened later clearly. This is the only way to allow yourself or your team to respond quickly to incidents and learn from them.</p><p>If you log too little or nothing at all, you may miss signs of attacks, such as suspicious file names (path traversal), repeated failed uploads, or unauthorised access. To prevent this, you should keep a few basic things in mind:</p><ul><li><p><strong>Log security-relevant events:</strong> Log all critical actions, such as file uploads, file accesses, path checks, or rejected requests, along with timestamps and context.</p></li><li><p><strong>Record errors and exceptions:</strong> Make sure that you display a message for all errors and exceptions and record the exact cause in the log. This allows you to search for specific sources of errors later.</p></li><li><p><strong>Use a central logging solution:</strong> Use proven logging frameworks like SLF4J in combination with Logback or Log4j2. This ensures that all log messages are recorded consistently, structured, and configurable for your environment.</p></li></ul><p>Below is a customised version of the application, which allows you to insert log messages at security-critical points. This gives you essential insights during operation and will enable you to react quickly to problems.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Files</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Path</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Paths</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.StandardCopyOption</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.slf4j.Logger</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.slf4j.LoggerFactory</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Logger</span><span class="w"/><span class="n">logger</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">LoggerFactory</span><span class="p">.</span><span class="na">getLogger</span><span class="p">(</span><span class="n">MainView</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">MainView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">MemoryBuffer</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">MemoryBuffer</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Upload</span><span class="w"/><span class="n">upload</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Upload</span><span class="p">(</span><span class="n">buffer</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">setMaxFiles</span><span class="p">(</span><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">upload</span><span class="p">.</span><span class="na">addSucceededListener</span><span class="p">(</span><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">sanitizeFileName</span><span class="p">(</span><span class="n">event</span><span class="p">.</span><span class="na">getFileName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Path</span><span class="w"/><span class="n">targetPath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"uploads"</span><span class="p">).</span><span class="na">resolve</span><span class="p">(</span><span class="n">fileName</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Prevent the path from being outside the upload directory</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">targetPath</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"uploads"</span><span class="p">).</span><span class="na">toAbsolutePath</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Invalid file path! Upload aborted."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="na">warn</span><span class="p">(</span><span class="s">"Invalid file path attempt: {}"</span><span class="p">,</span><span class="w"/><span class="n">targetPath</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">InputStream</span><span class="w"/><span class="n">inputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">buffer</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Create a secure temporary file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Path</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">createTempFile</span><span class="p">(</span><span class="s">"upload_"</span><span class="p">,</span><span class="w"/><span class="s">"tmp"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Files</span><span class="p">.</span><span class="na">copy</span><span class="p">(</span><span class="n">inputStream</span><span class="p">,</span><span class="w"/><span class="n">tempFile</span><span class="p">,</span><span class="w"/><span class="n">StandardCopyOption</span><span class="p">.</span><span class="na">REPLACE_EXISTING</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Move the temporary file to the target directory</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Files</span><span class="p">.</span><span class="na">createDirectories</span><span class="p">(</span><span class="n">targetPath</span><span class="p">.</span><span class="na">getParent</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Files</span><span class="p">.</span><span class="na">move</span><span class="p">(</span><span class="n">tempFile</span><span class="p">,</span><span class="w"/><span class="n">targetPath</span><span class="p">,</span><span class="w"/><span class="n">StandardCopyOption</span><span class="p">.</span><span class="na">REPLACE_EXISTING</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"File "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" uploaded successfully!"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"File {} successfully uploaded to {}"</span><span class="p">,</span><span class="w"/><span class="n">fileName</span><span class="p">,</span><span class="w"/><span class="n">targetPath</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">updateFileList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Error uploading file"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="na">error</span><span class="p">(</span><span class="s">"Error uploading file {}: {}"</span><span class="p">,</span><span class="w"/><span class="n">fileName</span><span class="p">,</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">(),</span><span class="w"/><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">add</span><span class="p">(</span><span class="n">upload</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">updateFileList</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">updateFileList</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">removeAll</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">File</span><span class="w"/><span class="n">folder</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"uploads"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">File</span><span class="o">[]</span><span class="w"/><span class="n">listOfFiles</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">folder</span><span class="p">.</span><span class="na">listFiles</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">listOfFiles</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="n">File</span><span class="w"/><span class="n">file</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">listOfFiles</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">file</span><span class="p">.</span><span class="na">isFile</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                   </span><span class="n">Anchor</span><span class="w"/><span class="n">downloadLink</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Anchor</span><span class="p">(</span><span class="s">"/uploads/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">file</span><span class="p">.</span><span class="na">getName</span><span class="p">(),</span><span class="w"/><span class="n">file</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">downloadLink</span><span class="p">.</span><span class="na">getElement</span><span class="p">().</span><span class="na">setAttribute</span><span class="p">(</span><span class="s">"download"</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">add</span><span class="p">(</span><span class="n">downloadLink</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"File {} added to download list"</span><span class="p">,</span><span class="w"/><span class="n">file</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">logger</span><span class="p">.</span><span class="na">warn</span><span class="p">(</span><span class="s">"Directory 'uploads' could not be read or is empty."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">sanitizeFileName</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="n">fileName</span><span class="p">.</span><span class="na">replaceAll</span><span class="p">(</span><span class="s">"[^a-zA-Z0-9._-]"</span><span class="p">,</span><span class="w"/><span class="s">"_"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The revised implementation uses the SLF4J (Simple Logging Facade for Java) logging API in combination with a specific implementation such as Logback. This separation of API and logging engine offers flexibility in choosing the underlying technology and facilitates integration into different runtime environments. The goal is to systematically record security-relevant events to ensure both transparency and traceability of security-critical actions during operation.</p><p>In the context of security-conscious web applications—especially those that work with user-generated content such as file uploads—comprehensive and structured logging is essential. You should explicitly record the following event types:</p><ul><li><p><strong>Path traversal tests (CWE-22):</strong> If a user attempts to access paths outside the intended memory area through manipulated input, you should report this with a WARN or higher log level. The logged information should include both the compromised path and the associated session or IP address to enable later correlation with other events.</p></li><li><p><strong>Successful file uploads:</strong> Every completed upload process should be documented in the log, including the cleaned-up file name, the target path in the file system, and optional context information such as the user ID or upload time. This allows for complete traceability and also serves as an audit trail.</p></li><li><p><strong>Upload error:</strong> If an exception occurs while saving the file (e.g., due to file system errors, access violations, or corrupted streams), you should log the full stack trace and all relevant metadata (file name, user context, time). This is essential for efficient error diagnosis and allows you to distinguish potential attack attempts from legitimate error cases.</p></li><li><p><strong>Dynamic update of the file view:</strong> Logging can also be helpful for internal system actions, such as refreshing the list of available files to track when a file becomes visible or whether problems occurred during processing (e.g., access errors in locked directories).</p></li></ul><p>Implementing these measures consistently allows you to detect and evaluate security-relevant incidents during ongoing operations promptly. Furthermore, a transparent logging strategy ensures that administrators and incident response teams have a solid database to rely on during an investigation. This strengthens your application&rsquo;s ability to respond to attacks and fulfils key requirements for traceability, auditing, and compliance in security-critical IT systems.</p><h3 id="summary">Summary</h3><p>We developed a functional file management application in Vaadin Flow in just a few steps. Users can upload files securely stored in the server directory and download them again. We used Java NIO to make file handling more efficient and secure. Additionally, we implemented safeguards against CWE-22 (Path Traversal), CWE-377 (Insecure Temporary File), and CWE-778 (Insufficient Logging) to ensure that attackers cannot gain unauthorised access to the file system, that temporary files are created securely, and that security-relevant events are comprehensively logged. This example demonstrates how easy it is to create a powerful user interface and implement server-side logic in Java with Vaadin Flow.</p><p>The next steps could be to extend the application, for example, by adding user permissions, customising the file overview, or integrating a search function for uploaded files. Vaadin offers numerous visual components to further improve the usability of your applications, but it is up to the developer to secure these features. Furthermore, we could extend the application with features such as drag-and-drop for file uploads, user role management, or a connection to a database for indexing files and storing metadata. However, with each additional feature, there are also additional attack vectors. It&rsquo;s always important to find the right balance.</p><p>By enhancing security measures such as implementing robust file validation, sufficient logging mechanisms, and using Java NIO, we ensure that the application remains secure and efficient for both the developer and end users.</p><p>We&rsquo;ll explore different aspects in the following parts, so it remains exciting.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Uncategorized</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2025/05/ChatGPT-Image-20.-Mai-2025-17_26_14.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/05/ChatGPT-Image-20.-Mai-2025-17_26_14.png"/><enclosure url="https://svenruppert.com/images/2025/05/ChatGPT-Image-20.-Mai-2025-17_26_14.png" type="image/jpeg" length="0"/></item><item><title>Open-hearted bytecode: Java Instrumentation API</title><link>https://svenruppert.com/posts/open-hearted-bytecode-java-instrumentation-api/</link><pubDate>Fri, 11 Apr 2025 17:06:44 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/open-hearted-bytecode-java-instrumentation-api/</guid><description>What is the Java Instrumentation API? The Java Instrumentation API is part of the java.lang.instrument package and allows you to change or analyse class bytecode at runtime. It is particularly intended for the development of profilers, agents, monitoring tools, or even dynamic security mechanisms that need to intervene deeply in a Java application&rsquo;s behaviour without changing the source code itself.</description><content:encoded>&lt;![CDATA[<h2 id="what-is-the-java-instrumentation-api">What is the Java Instrumentation API?</h2><p>The Java Instrumentation API is part of the java.lang.instrument package and allows you to change or analyse class bytecode at runtime. It is particularly intended for the development of profilers, agents, monitoring tools, or even dynamic security mechanisms that need to intervene deeply in a Java application&rsquo;s behaviour without changing the source code itself.</p><ol><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#what-is-the-java-instrumentation-api">What is the Java Instrumentation API?</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#what-are-the-advantages-and-disadvantages">What are the advantages and disadvantages?</a><ol><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#advantages">Advantages</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#disadvantages">Disadvantages</a></li></ol></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#what-are-the-performance-implications">What are the performance implications?</a><ol><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#influence-during-class-loading">Influence during class loading</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#impact-at-runtime">Impact at runtime</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#memory-and-garbage-collector-effects">Memory and garbage collector effects</a></li></ol></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#what-security-implications-arise">What security implications arise?</a><ol><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#security-implications-in-detail">Security implications in detail</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#relevant-cwes-common-weakness-enumerations">Relevant CWEs (Common Weakness Enumerations)</a></li></ol></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#a-practical-example">A practical example</a><ol><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#1-rest-service-without-external-dependencies">1. REST service without external dependencies</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#2-java-agent-for-instrumentation">2. Java Agent for instrumentation</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#how-does-the-whole-thing-start">How does the whole thing start?</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#why-do-you-need-one-manifest-mf">Why do you need one? MANIFEST.MF?</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#what-does-a-valid-one-look-like-manifest-mf-out-of">What does a valid one look like? MANIFEST.MF out of?</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#where-and-how-is-the-file-created">Where and how is the file created?</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#how-does-this-work-at-runtime">How does this work at runtime?</a></li><li><a href="https://svenruppert.com/2025/04/11/open-hearted-bytecode-java-instrumentation-api/#what-and-why-is-this-happening">What and why is this happening?</a></li></ol></li></ol><p>At the heart of this API is the concept of<em>Java Agents</em>. An agent is a special component that is activated when the JVM starts or at runtime (via the so-called<em>Attach API</em>) can be loaded. The agent then uses an Instrumentation-Interface passed by the JVM launcher. This interface allows the agent to inspect already loaded classes, transform new classes or influence the behavior of the JVM - for example, by inserting hooks, measuring call times or injecting security checks.</p><p>The basic task is to transform the bytecode during the classloading process without violating the semantics of the Java language. Technically, this is done by a so-called ClassFileTransformer, which is registered and can manipulate the bytecode every time a class is loaded before it is interpreted or compiled by the JVM.</p><p>A typical entry point for a static agent is a class with a premain(String agentArgs, Instrumentation inst)-Method. With dynamic agents, this comes instead agentmain(String agentArgs, Instrumentation inst)method to use. Both variants allow transformers to be registered and thus influence the behavior of the application below the language level.</p><p>Using the Instrumentation API requires a deep understanding of the JVM architecture and Java class loading mechanism. At the same time, it also opens up unique possibilities for runtime analysis and modification that would not be achievable using normal means in Java, and in a way that can be elegantly integrated into existing applications without having to recompile them.</p><h2 id="what-are-the-advantages-and-disadvantages">What are the advantages and disadvantages?</h2><h3 id="advantages">Advantages</h3><p>A key advantage lies in the ability to<strong>manipulate</strong><strong>bytecodes transparently</strong> : You can change the behavior of existing classes without touching their source code. This is particularly relevant in safety-critical or highly dynamic environments, such as when inserting logging, tracing or metrics code. The API also enables the so-called<em>Class Retransformation</em> , which means that already loaded classes can be subsequently changed under certain conditions. This feature is used intensively in conjunction with tools such as JRebel or modern observability solutions such as OpenTelemetry.</p><p>Another advantage is i<strong>ntegration without source code changes</strong>. An agent can be started using the Java process or added at runtime without the target application even knowing it is being instrumented. This is particularly essential for debugging, monitoring or security solutions.</p><p>In addition, the Instrumentation API has been<strong>Part of the JDK since Java 5</strong> and requires no external dependencies in security-conscious environments—for example, ​​critical infrastructure or the government environment—this can be a significant argument for but also against its use.</p><h3 id="disadvantages">Disadvantages</h3><p>The power of the Instrumentation API inevitably brings significant disadvantages. First and foremost is the<strong>Complexity of bytecode manipulation</strong>. Although there are libraries such as ASM or Byte Buddy that make working with bytecode easier, getting started remains deep in the JVM&rsquo;s engine room and requires a sound understanding of the Java class structure, class loading, and bytecode specification.</p><p>A second risk concerns<strong>Stability and predictability</strong> : Interference with the bytecode can lead to subtle errors, for example, when method signatures are changed, security checks are bypassed, or synchronisation blocks are manipulated. Such errors are difficult to test and can only occur under certain runtime conditions, drastically increasing the debugging effort.</p><p>Those too<strong>Compatibility across different JVM versions</strong> is a problem. Not every JVM behaves identically when it comes to class loading or transformation. Particularly with GraalVM or in AOT environments, certain features of the Instrumentation API may not work or only work to a limited extent. This particularly applies to dynamic reloading of classes or access to low-level JVM internals.</p><p>After all, p<strong>erformance overhead</strong> should not be neglected. While simply registering an agent is usually very efficient, when transformers perform complex operations on every classload or inject metrics, this can result in measurable startup or runtime delays, especially in I/O-intensive server applications.</p><p>The Java Instrumentation API is a double-edged sword: It offers a unique opportunity to intervene in the behavior of a Java application deeply, but at the price of increased complexity, potential instability, and difficult maintainability. Its use should, therefore, be well justified, targeted, and accompanied by appropriate testing and logging infrastructure. However, it remains an indispensable tool in safety-critical or highly dynamic contexts where application code modification is impossible.</p><h2 id="what-are-the-performance-implications">What are the performance implications?</h2><p>The performance implications of the Java Instrumentation API are complex and depend heavily on the respective use case, the complexity of the transformations carried out, and the type of target application. The Instrumentation API influences performance during three phases: during class loading, at runtime of the instrumented classes, and in connection with memory or security-relevant operations.</p><h3 id="influence-during-class-loading">Influence during class loading</h3><p>The most significant immediate impact occurs when the class loader loads a class. As soon as a ClassFileTransformer has been registered, it is called every time a new class is loaded. At this moment, the JVM passes the class&rsquo;s original bytecode to the Transformer, which can modify it or return it unchanged. The duration of this transformation directly affects the application&rsquo;s start time or the dynamic reloading of classes.</p><p>Complex transformations, especially those performed with ASM or similar bytecode libraries, can quickly generate measurable overhead, especially in framework-based applications with thousands of classes. If additional logging, tracing or code injections are carried out in each method, the loading time increases exponentially because each method block has to be processed individually.</p><h3 id="impact-at-runtime">Impact at runtime</h3><p>Additional overhead may also arise at runtime, depending on how the bytecode was changed. This is particularly true if the Transformer inserts additional control flow, such as logging instructions, time measurement, additional validations or security-related checks. For example, such insertions can extend the execution of each method by several nanoseconds to milliseconds, which can lead to massive performance losses in methods called at high frequencies (e.g. in I/O loops or business logic).</p><p>A typical example is the so-called method entry/exit tracing, where logging code is injected before and after each method. Even if this logging code itself does not actively write, but only sets passive markers, a memory and CPU load can still add up under high load.</p><p>In addition, the instrumentation can also<strong>JIT optimizations affect the JVM</strong> , especially when unpredictable control flows are inserted or methods are artificially inflated. This can lead to certain hotspots no longer being converted into natively optimised code, with corresponding consequences for execution time.</p><h3 id="memory-and-garbage-collector-effects">Memory and garbage collector effects</h3><p>Introducing additional object state during instrumentation—for example, through static caches, trace information, or associated metadata—can increase heap usage. This becomes particularly critical if weak or soft references are not managed correctly or if new classes with slightly different structures are continually created, which, in the worst case, can lead to an OutOfMemoryError in the class memory.</p><p>The Java Instrumentation API, when used efficiently and purposefully, can be used with minimal impact on performance. However, this requires that the registered transformers are kept as lean as possible, unnecessary transformations are avoided, and classes are filtered in a targeted manner. At the same time, one should always keep in mind that even small changes in the bytecode can have far-reaching effects on the JVM&rsquo;s optimization strategies and memory behavior. Therefore, careful benchmarking and targeted monitoring are essential if you want to use the API in production systems.</p><h2 id="what-security-implications-arise">What security implications arise?</h2><p>Because it can manipulate classes at runtime, remove security checks, or inject new bytecode, it opens up an attack surface far beyond what typical application code can do. It therefore represents a potential gateway for privilege escalation, code injection and persistence mechanisms, such as those observed with Advanced Persistent Threats (APT) or malicious agents.</p><h3 id="security-implications-in-detail">Security implications in detail</h3><p>First of all, it should be noted that the Instrumentation API can deliberately operate past the JVM sandbox. Once an agent is active - be it when it is started via the -javaagent argument or dynamically via the Attach API - it has almost complete control over all loaded classes in the context of the running JVM. It can remove security checks, alter stack traces, redirect control flow, or extract sensitive information from memory. Also, the transformation of classes from security-relevant packages such as java.lang.reflect or javax.crypto is possible if no SecurityManager restrictions are active (which, however, have been deprecated since Java 17 and are completely eliminated from Java 21).</p><p>The Attach API is particularly critical because it allows arbitrary Java processes on the local system to be provided with an agent at runtime—as long as the same user context is present. This means that an attacker who gains access to a shell account could compromise any of that user&rsquo;s Java processes without that process having to be prepared for this. This scenario corresponds to a form of<em>runtime privilege escalation</em> at the process level.</p><h3 id="relevant-cwes-common-weakness-enumerations">Relevant CWEs (Common Weakness Enumerations)</h3><p>Concerning the Java Instrumentation API, several CWE categories can be named that are relevant in the context of JVM and agent manipulation:</p><p>**CWE-94: Improper Control of Generation of Code (&lsquo;Code Injection&rsquo;)
**Suppose an agent is used to generate or load new bytecode from external sources at runtime (e.g., network data, configuration files, or user input). In that case, this is a classic code injection, which can lead to a full takeover of the JVM.</p><p>**CWE-250: Execution with Unnecessary Privileges
**By default, agents operate with the full rights of the Java process. Malicious agents can exploit unnecessary privileges if appropriate access protection is not implemented (e.g., through user separation or restrictive process isolation).</p><p>**CWE-269: Improper Privilege Management
**Improper use of an agent to transform security-critical classes (e.g. ClassLoader, AccessController, Cryptographic Provider) can lead to a breach of the Java security model.</p><p>**CWE-272: Least Privilege Violation
**If applications unnecessarily activate the Attach API or even provide their agent interfaces, this violates the principle of minimum rights allocation.</p><p>**CWE-749: Exposed Dangerous Method or Function
**An improperly configured application, for example, making the Attach API available via an HTTP endpoint (e.g. in self-built monitoring solutions) opens dangerous functions to external attackers.</p><p>**CWE-798: Use of Hard-coded Credentials
**In some dynamic agents, the transformation of security-relevant classes uses internal access or passwords that remain in the bytecode. These are easily extracted and can lead to the compromise of other systems.</p><p>From a security perspective, the Java Instrumentation API can be classified as a privileged interface, which, if handled incorrectly, leads to significant opportunities for attacks. Your use should therefore always be carried out with<strong>procedural and technical controls</strong> flanked - for example, by isolating agents in dedicated containers, by restrictive file system and user rights, and by consistently deactivating the Attach API in production environments using the JVM option -Dcom.sun.management.jmxremote=false or -XX:+DisableAttachMechanism.</p><p>Careful use of instrumentation is essential, particularly in security-critical scenarios, such as processing personal data or in distributed microservices architectures. Using this API without a sound security concept can quickly become a high-risk attack vector.</p><h2 id="a-practical-example">A practical example</h2><p>A simple but well-founded example of using the Java Instrumentation API in the context of a REST service can be implemented using onboard tools in the JDK, particularly using the com.sun.net.httpserver.HttpServer for server functionality. The goal is to write a Java agent that transforms all REST endpoints during class loading so that they automatically insert logging statements at the start of each method without changing the endpoints&rsquo; original code.</p><p>The application,, therefore, consists of two parts:</p><ol><li>The REST service based on HttpServer</li><li>A Java agent that works via Instrumentation, a ClassFileTransformer registered</li></ol><h3 id="1-rest-service-without-external-dependencies">1. REST service without external dependencies</h3><p>The aim of this service is to provide simple license management based on GET and POST requests:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.sun.net.httpserver.HttpServer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.sun.net.httpserver.HttpHandler</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.sun.net.httpserver.HttpExchange</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.OutputStream</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.InetSocketAddress</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">LicenseRestServer</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">HttpServer</span><span class="w"/><span class="n">server</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">HttpServer</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">InetSocketAddress</span><span class="p">(</span><span class="n">8080</span><span class="p">),</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">server</span><span class="p">.</span><span class="na">createContext</span><span class="p">(</span><span class="s">"/license"</span><span class="p">,</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">LicenseHandler</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">server</span><span class="p">.</span><span class="na">setExecutor</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">server</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Server started on port 8080"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">class</span><span class="nc">LicenseHandler</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HttpHandler</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handle</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">exchange</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="s">"GET"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestMethod</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">response</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Current license: DEMO-1234-5678"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">200</span><span class="p">,</span><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">getBytes</span><span class="p">().</span><span class="na">length</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseBody</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="na">getBytes</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">405</span><span class="p">,</span><span class="w"/><span class="o">-</span><span class="n">1</span><span class="p">);</span><span class="w"/><span class="c1">// Method Not Allowed</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This application starts a local REST server and responds GET /license with a static license key.</p><h3 id="2-java-agent-for-instrumentation">2. Java Agent for instrumentation</h3><p>Now, a static Java agent is implemented that runs when the JVM starts -javaagent can be integrated. The goal is to have the class at runtime LicenseHandler, to transform and to precede each method with simple logging.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.lang.instrument.ClassFileTransformer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.lang.instrument.Instrumentation</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.ProtectionDomain</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SimpleLoggingAgent</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">premain</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">agentArgs</span><span class="p">,</span><span class="w"/><span class="n">Instrumentation</span><span class="w"/><span class="n">inst</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"[Agent] Initializing agent with arguments: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">agentArgs</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inst</span><span class="p">.</span><span class="na">addTransformer</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">LoggingTransformer</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">class</span><span class="nc">LoggingTransformer</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">ClassFileTransformer</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="nf">transform</span><span class="p">(</span><span class="n">Module</span><span class="w"/><span class="n">module</span><span class="p">,</span><span class="w"/><span class="n">ClassLoader</span><span class="w"/><span class="n">loader</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">className</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Class</span><span class="o">&lt;?&gt;</span><span class="w"/><span class="n">classBeingRedefined</span><span class="p">,</span><span class="w"/><span class="n">ProtectionDomain</span><span class="w"/><span class="n">protectionDomain</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">classfileBuffer</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">className</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">className</span><span class="p">.</span><span class="na">contains</span><span class="p">(</span><span class="s">"LicenseRestServer$LicenseHandler"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"[Agent] Transforming class: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">className</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Here you could modify real bytecode (e.g. with ASM)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// For the purposes of this example, we return the original bytecode.</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">classfileBuffer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this minimal form, the transformer does not make any real bytecode changes, but it shows the basic process: when loading the class LicenseHandler the transform method is called. An extended version could be done here using the ClassReader and ClassWriter out of org.objectweb.asm Inject targeted logging at the beginning of each method.</p><h3 id="how-does-the-whole-thing-start">How does the whole thing start?</h3><ol><li>Compiling both classes into separate JARs:</li></ol><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="o">*</span><span class="w"/><span class="n">license</span><span class="o">-</span><span class="n">service</span><span class="p">.</span><span class="na">jar</span><span class="w"/><span class="n">contains</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">application</span><span class="p">.</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">*</span><span class="w"/><span class="n">agent</span><span class="p">.</span><span class="na">jar</span><span class="w"/><span class="n">contains</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">agent</span><span class="w"/><span class="n">and</span><span class="w"/><span class="n">a</span><span class="w"/><span class="n">MANIFEST</span><span class="p">.</span><span class="na">MF</span><span class="w"/><span class="n">with</span><span class="w"/><span class="n">Premain</span><span class="o">-</span><span class="n">Class</span><span class="p">:</span><span class="w"/><span class="n">SimpleLoggingAgent</span><span class="p">.</span><span class="w"/></span></span></code></pre></div></div><ol start="2"><li>Starting the application with the agent:</li></ol><p><strong>java -javaagent:agent.jar -jar license-service.jar</strong></p><p>When starting, the JVM loads the agent, then registers the transformer. As soon as the LicenseHandlerclass is loaded, the transformer gets access to its bytecode.</p><p>The<strong>Manifest file</strong> plays a central role in the context of a Java agent because it explicitly tells the JVM when it starts<strong>which class initializes the agent</strong> – i.e. where the method is premain() or agentmain() located. Without this information, the JVM cannot recognize the agent class and will ignore the agent at startup. This is the technical reason why the manifest file is mandatory if you want to build a Java agent.</p><h3 id="why-do-you-need-one-manifestmf">Why do you need one? MANIFEST.MF?</h3><p>If an agent over -javaagent:path/to/agent.jar is started, the JVM reads the JAR and searches in META-INF/MANIFEST.MF by one of the following attributes:</p><p><strong>Premain-Class</strong> : Specifies the class that will be used at JVM startup (before main()) is executed.</p><p><strong>Agent-Class</strong> : Specifies the class that belongs to. A<em>more dynamic</em> Agent attachment is used via the Attach API.</p><p><strong>Can-Redefine-Classes, Can-Retransform-Classes, etc.</strong> : Optional, to specify additional capabilities.</p><p><strong>Without a correctly placed one Premain-Class attribute, the agent cannot be started.</strong> The JVM has no mechanism to guess or scan this automatically.</p><h3 id="what-does-a-valid-one-look-like-manifestmf-out-of">What does a valid one look like? MANIFEST.MF out of?</h3><p>A minimal manifest file for a static Java agent might look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">yaml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">Manifest-Version</span><span class="p">:</span><span class="w"/><span class="m">1.0</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nt">Premain-Class</span><span class="p">:</span><span class="w"/><span class="l">com.svenruppert.securecoding.agent.SimpleLoggingAgent</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nt">Can-Redefine-Classes</span><span class="p">:</span><span class="w"/><span class="kc">true</span></span></span></code></pre></div></div><p>Ensure that each line has a line break (\n) end and that no line break is forgotten. Spaces are also meaningful – there must be exactly one space between the attribute and the value.</p><h3 id="where-and-how-is-the-file-created">Where and how is the file created?</h3><p>You can create the MANIFEST.MF file in several ways. In this example, I&rsquo;m using Maven to declare the necessary parameters and attributes.</p><p><strong>Example for pom.xml :</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;build&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;plugins&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;plugin&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;artifactId&gt;</span>maven-jar-plugin<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;configuration&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;archive&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;manifestEntries&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;Premain-Class&gt;</span>com.svenruppert.securecoding.agent.SimpleLoggingAgent<span class="nt">&lt;/Premain-Class&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;Can-Redefine-Classes&gt;</span>true<span class="nt">&lt;/Can-Redefine-Classes&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/manifestEntries&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/archive&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/configuration&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/plugin&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/plugins&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/build&gt;</span></span></span></code></pre></div></div><h3 id="how-does-this-work-at-runtime">How does this work at runtime?</h3><p>If you start the JVM with the agent:</p><p><strong>java -javaagent:simple-agent.jar -jar my-rest-service.jar</strong></p><p>The JVM does the following:</p><ul><li>Read the JAR simple-agent.jar.</li><li>Opens META-INF/MANIFEST.MF.</li><li>Finds Premain-Class: SimpleLoggingAgent.</li><li>Loads this class.</li><li>Leads premain(String args, Instrumentation inst) out, still before the main()method of the actual program is started.</li></ul><p>This gives the agent the chance to influence the behavior of the program before the first line of business logic is executed. This is precisely where his power lies – and the risk. The manifest file is the link between the JVM startup mechanism and the agent code. It is essential for Java agents because it tells the JVM<strong>which code actually contains agent logic</strong>. Their correct creation is a prerequisite for any form of static instrumentation. If you set it incorrectly or not at all, it will have no effect when the agent is started - the JVM will then ignore the agent without comment.</p><h3 id="what-and-why-is-this-happening">What and why is this happening?</h3><p>This architecture shows the strength of the Instrumentation API: without changing a line of application code, functionalities such as logging, tracing, or metrics can be injected at runtime. This happens early in the JVM&rsquo;s life cycle (before the actual program starts), which means that all future classes can be affected if specifically addressed.</p><p>Even without additional libraries, this setup demonstrates the core mechanism of the Instrumentation API: runtime manipulation of classes in a production-level environment. It shows how profound and dangerous this mechanism can be if there are no appropriate protection mechanisms at the JVM or operating system level.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><media:content url="https://svenruppert.com/images/2025/04/ChatGPT-Image-11.-Apr.-2025-17_02_02-gross.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/04/ChatGPT-Image-11.-Apr.-2025-17_02_02-gross.jpeg"/><enclosure url="https://svenruppert.com/images/2025/04/ChatGPT-Image-11.-Apr.-2025-17_02_02-gross.jpeg" type="image/jpeg" length="0"/></item><item><title>Synchronous in Chaos: How Parallel Collectors Bring Order to Java Streams</title><link>https://svenruppert.com/posts/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/</link><pubDate>Tue, 08 Apr 2025 20:25:45 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/</guid><description>Sometimes it&rsquo;s not enough for something to work - it has to work under load. In modern applications that process large amounts of data, the Streams API in Java provides developers with an elegant, declarative tool to transform, filter, and aggregate data in pipelines. ​​Describing complex data operations with just a few lines is seductive and realistic. But what happens when these operations encounter millions of entries? When should execution be done in multiple threads in parallel to save time and effectively use multi-core systems?</description><content:encoded>&lt;![CDATA[<p>Sometimes it&rsquo;s not enough for something to work - it has to work under load. In modern applications that process large amounts of data, the Streams API in Java provides developers with an elegant, declarative tool to transform, filter, and aggregate data in pipelines. ​​Describing complex data operations with just a few lines is seductive and realistic. But what happens when these operations encounter millions of entries? When should execution be done in multiple threads in parallel to save time and effectively use multi-core systems?</p><ol><li><a href="https://svenruppert.com/2025/04/08/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/#basics-collector-and-concurrency">Basics: Collector and concurrency</a></li><li><a href="https://svenruppert.com/2025/04/08/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/#criteria-for-parallelizable-collectors">Criteria for parallelizable collectors</a></li><li><a href="https://svenruppert.com/2025/04/08/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/#examples-from-the-java-standard-library">Examples from the Java standard library</a></li><li><a href="https://svenruppert.com/2025/04/08/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/#own-parallel-collector-implementations">Own parallel collector implementations</a></li><li><a href="https://svenruppert.com/2025/04/08/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/#best-practices-for-productive-use">Best practices for productive use</a></li><li><a href="https://svenruppert.com/2025/04/08/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/#application-scenario-license-analysis-in-the-benchmark">Application scenario: License analysis in the benchmark</a><ol><li><a href="https://svenruppert.com/2025/04/08/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/#the-data-model">The data model</a></li><li><a href="https://svenruppert.com/2025/04/08/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/#the-test-setup">The test setup</a></li></ol></li><li><a href="https://svenruppert.com/2025/04/08/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/#comparison-with-alternative-parallelism-concepts">Comparison with alternative parallelism concepts</a></li><li><a href="https://svenruppert.com/2025/04/08/synchronous-in-chaos-how-parallel-collectors-bring-order-to-java-streams/#conclusion-and-outlook">Conclusion and outlook</a></li></ol><p>At this point, a concept that often receives too little attention is the collector. The element at the end of a stream pipeline determines<strong>what should happen with the processed data</strong>. And even though the API seems simple – collect(Collectors.toList()) – there is an architecture behind it that brings its challenges when executed in parallel.</p><p>This text is, therefore, not just about the syntax or mechanics of collectors but rather a deep understanding of the conditions under which they can be used correctly and efficiently in parallel. We look at standard JDK solutions, discuss our implementations, show typical errors, and ultimately ask ourselves: How parallel can a collector be without becoming dangerous?</p><h2 id="basics-collector-and-concurrency">Basics: Collector and concurrency</h2><p>At first glance, Java&rsquo;s Streams API suggests that collecting results—the so-called terminal aggregation—can easily be carried out in parallel. But behind the method collect(&hellip;) hides more than just syntactic convenience. It is a coordinated collaboration between a data stream and a so-called collector—an object that forms a whole from individual parts.</p><p>A collector essentially consists of four functional components: the supplier, which provides a new cache for each subprocess; the accumulator, which feeds elements into this cache; the combiner, which merges multiple caches into one; and finally, the finisher, which produces the result. While supplier and accumulator are also essential in sequential streams, the combiner only comes into action when several threads have collected independently of each other, i.e., one parallelStream().</p><p>Here lies the first fundamental difference between sequential and parallel processing: in a sequential stream, it is sufficient to accumulate step by step into a single memory. In the parallel variant, on the other hand, several buffers are created that are isolated from one another, the contents of which must later be merged into a final result without conflict. This merging happens through the combiner, and it is precisely at this point that it is decided whether a collector is suitable for parallel use or not.</p><p>This suitability depends on several properties: The operations must be associative, i.e., deliver the same result regardless of the combination of the intermediate results. In addition, there must not be a shared state without synchronization. Last but not least, the individual steps must remain deterministic and free of side effects—otherwise, parallelisation quickly becomes a source of subtle errors.</p><p>Knowing these structural requirements is the first step toward consciously using parallel processing. Only if you understand how the collector and stream work together can you estimate when a performance gain is possible and when you will get unstable or simply wrong results instead.</p><h2 id="criteria-for-parallelizable-collectors">Criteria for parallelizable collectors</h2><p>Let&rsquo;s imagine a stream running in parallel—perhaps over a large dataset divided into several segments. Each segment is now processed independently. What sounds trivial has profound implications: As soon as several threads collect at the same time, their intermediate results must not get in the way. The responsibility for correctness lies with the collector—or, more precisely, with its structural and functional design.</p><p>The first fundamental property is<strong>Associativity</strong>. A combiner call must produce consistent results regardless of the order. combine (a, b) and combine (b, a) must produce equivalent results. This is necessary because the order of combination in a parallel context depends on the scheduler and is, therefore, unpredictable.</p><p>A second central point concerns<strong>Access to memory structures</strong>. A potential hotspot for race conditions arises when a collector uses a shared, mutable state during accumulation, such as an out-of-sync list or map. The collector must either work exclusively with local, thread-isolated caches or rely on concurrent data structures such as b: ConcurrentHashMap, LongAdder, or explicitly synchronized wrappers.</p><p>In addition, it is also<strong>deterministic</strong> and an essential criterion: parallel execution must not lead to different results, neither in terms of content nor structure. Caution is advised, especially with non-deterministic structures like HashSet or HashMap, as the iteration order can vary, as with Collectors.joining() or Collectors.toMap() becomes problematic when the application relies on order.</p><p>These requirements—associativity, isolated state, and determinism—form the technical touchstone for parallel collectors. They are not optional but fundamental. Anyone who ignores them risks difficult-to-reproduce errors, incomplete results, or high-performance but semantically incorrect output.</p><p>Concurrency in Java Streams is powerful but not trivial. Only if a collector meets these criteria does it become one parallelStream() more than a placebo effect.</p><h2 id="examples-from-the-java-standard-library">Examples from the Java standard library</h2><p>An obvious way to make the abstract concept of parallel collectors tangible is through the collectors already included in the Java standard library. Many developers use Collectors.toList(), toSet(), or joining() almost every day—but rarely with knowledge of whether and how these collectors behave in a parallel context.</p><p>A simple example: The Collector Collectors.toList() uses an internal ArrayList. This is not thread-safe; therefore, the result when used in parallel is potentially inconsistent unless the buffers are isolated internally.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public static<span class="nt">&lt;T&gt;</span></span></span><span class="line"><span class="cl">Collector<span class="nt">&lt;T</span><span class="err">,</span><span class="err">?,</span><span class="err">List&lt;T</span><span class="nt">&gt;</span>&gt; toList() {</span></span><span class="line"><span class="cl"> return new CollectorImpl<span class="err">&lt;</span>&gt;(ArrayList::new, List::add,</span></span><span class="line"><span class="cl"> (left, right) -&gt; { left.addAll(right); return left; },</span></span><span class="line"><span class="cl"> CH_ID);</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>In fact, this collector still works correctly in parallel streams because the Streams API allocates each thread its accumulation area and merges only at the end via a combined merge process. The crucial point lies not in the data structure itself but in its controlled isolation.</p><p>It turns out to be less robust: Collectors.groupingBy(&hellip;). This variant is based on one HashMap and is not designed for concurrent access. This collector will be in one parallelStream(). Used without suitable protective measures, there is a risk of race conditions. The standard solution is Collectors.groupingByConcurrent(&hellip;), internal ConcurrentHashMap, designed for simultaneous access.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public static<span class="nt">&lt;T</span><span class="err">,</span><span class="err">K</span><span class="nt">&gt;</span></span></span><span class="line"><span class="cl">Collector<span class="nt">&lt;T</span><span class="err">,</span><span class="err">?,</span><span class="err">ConcurrentMap&lt;K,</span><span class="err">List&lt;T</span><span class="nt">&gt;</span>&gt;&gt;</span></span><span class="line"><span class="cl">groupingByConcurrent(Function<span class="err">&lt;</span>? super T, ? extends K&gt; classifier) {</span></span><span class="line"><span class="cl"> return groupingByConcurrent(classifier, ConcurrentHashMap::new, toList());</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>A look at the signature of this method already shows the intention:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Map<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;String</span><span class="nt">&gt;</span>&gt; result = namen.parallelStream()</span></span><span class="line"><span class="cl"> .collect(Collectors.groupingByConcurrent(String::length));</span></span></code></pre></div></div><p>In this example, strings are grouped by their length in a way that can be processed in parallel. Crucially, both the map implementation and the accumulation process are thread-safe.</p><p>Equally interesting Collectors.toConcurrentMap(&hellip;), which is explicitly intended to aggregate large sets of key-value pairs in parallel. The combination of key conflicts and the correct handling of merge functions is particularly interesting here.</p><p>These examples conclude that not every standard collector is suitable for parallel use per se. Just because a method from the Collectors-kit, this does not mean that it will work correctly in every execution configuration. The context decides - and with it the data structure used, the behavior of the combiner and the type of accumulation.</p><p>So, if you want to get more than one result from a stream—a correct and high-performance result—you should choose your collector just as carefully as you choose the filter criterion at the beginning of the pipeline.</p><h2 id="own-parallel-collector-implementations">Own parallel collector implementations</h2><p>As powerful as the Java Standard Library&rsquo;s prebuilt collectors are, they are sometimes not sufficient for specific needs. Especially when domain-specific aggregations, specialized data structures, or non-trivial reduction logic are required, it is worth considering the possibility of creating your own collector implementations.</p><p>A custom collector is usually created using the static method Collector.of(&hellip;) created. This method expects five parameters: one Supplier<A>, which creates a new accumulator; a BiConsumer&lt;A, T&gt;, which inserts an element into the accumulator; a BinaryOperator<A> for combining two accumulators; optionally one Function&lt;A, R&gt; to convert the result; and a Collector.Characteristics&hellip;-Array containing meta information like CONCURRENT or UNORDERED provides.</p><p>A simple but meaningful collector could, for example, collect strings in parallel ConcurrentLinkedQueue collect:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Collector<span class="nt">&lt;String</span><span class="err">,</span><span class="err">?,</span><span class="err">Queue&lt;String</span><span class="nt">&gt;</span>&gt; toConcurrentQueue() {</span></span><span class="line"><span class="cl"> return Collector.of(</span></span><span class="line"><span class="cl"> ConcurrentLinkedQueue::new,</span></span><span class="line"><span class="cl"> Queue::add,</span></span><span class="line"><span class="cl"> (left, right) -&gt; { left.addAll(right); return left; },</span></span><span class="line"><span class="cl"> Collector.Characteristics.CONCURRENT, Collector.Characteristics.UNORDERED</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This collector is both CONCURRENT and UNORDERED, which means it can be written to by multiple threads simultaneously without guaranteeing the insertion order. What is important is that ConcurrentLinkedQueue acts as a thread-safe data structure, and the operation addAll is also incidentally uncritical.</p><p>However, more complex scenarios, such as the parallel determination of statistical key figures (minimum, maximum, average) across a data set, are also conceivable. In such cases, one can record serve as an accumulator structure that already encapsulates all the required partial states. The combiner then only has to consolidate these structures field by field.</p><p>Own collector implementations force you to deal intensively with the parallelizability of the data structures used and the combinability of the aggregation logic. This is not a disadvantage, but a valuable learning effect, because only those who understand what a collector does inside can use it consciously and safely.</p><h2 id="best-practices-for-productive-use">Best practices for productive use</h2><p>Anyone who wants to use collectors productively in a parallel context should consider some proven strategies - not as rigid rules, but as a framework for robust and efficient implementations.</p><p>A first principle is:<strong>Only parallelize if there is real benefit to be expected</strong>. Small amounts of data, trivial transformations or IO-bound processes usually do not benefit from parallelStream(). On the contrary: the overhead of thread management can even exceed the potential performance gain. Parallelization is only worthwhile if the amount of data to be processed is sufficiently large and the operations are CPU-intensive.</p><p>Second:<strong>Use only thread-safe or isolated data structures</strong>. This means that each thread uses its accumulator, which the Streams API supports internally, or explicitly concurrent data structures such as ConcurrentHashMap, ConcurrentLinkedQueue, or atomic wrappers can be used.</p><p>Third:<strong>Select collectors specifically</strong>. The standard library provides groupingByConcurrent, toConcurrentMap or mapping powerful tools specifically designed for parallel use. Anyone who also develops their solutions should pay particular attention to this combiner and the associativity of logic.</p><p>Fourth:<strong>Validate results</strong> , especially for new or complex pipelines. Parallel streams do not behave deterministically in execution, so tests in different utilization scenarios and under varying loads are necessary. This is especially true if collectors were developed or adapted in-house.</p><p>Last but not least:<strong>Measure instead of assume</strong>. Tools such as JMH (Java Microbenchmark Harness), Flight Recorder, or async-profiler help make realistic statements about performance advantages. Parallelization without metrics is like flying blind with a tailwind—it may be faster, but possibly in the wrong direction.</p><p>In summary, Parallel collectors are powerful tools, but they can only develop their potential if they are used consciously, well-founded, and with an eye on data structure, semantics, and execution context.</p><h2 id="application-scenario-license-analysis-in-the-benchmark">Application scenario: License analysis in the benchmark</h2><p>In our example, we will now look at the following scenario. We will evaluate fictitious license information. These consist of several attributes that lend themselves to automated analysis: Is the license valid? Is this a trial version? Which country does the license come from? How long was it valid?</p><p>Instead of answering these questions individually and sequentially, the Java Streams API allows us to view the data as a stream and convert it into a consolidated analysis in a collector. But what happens if we analyse not 100, but 100,000 or 1,000,000 such license objects? A simple one is enough stream(), or is the parallel variant worth it? parallelStream()?</p><p>This is precisely where our experiment comes in: We want to measure the amount of data for which a parallel collector is noticeably worthwhile, both in terms of runtime and resource utilization. We use a specially developed collector that aggregates various metrics:</p><ul><li>Total number of valid licenses</li><li>Share of test licenses</li><li>Average duration in days</li><li>Distribution of licenses by country</li><li>The oldest and youngest licenses issued</li></ul><p>To capture these values ​​as realistically as possible, we use a benchmark with JMH, the official microbenchmark tool of the OpenJDK community. The aim is to develop a sense of when parallel collectors actually offer added value and under which conditions they may even prove inefficient.</p><h3 id="the-data-model">The data model</h3><p>The benchmark&rsquo;s data model is based on a practical license management system, such as those typically used in software products, platform services, or license servers. It consists of a compact but expressive Record class called LicenseInfo, which encapsulates all the essential information of a single software license.</p><p>At the center is the<strong>precise product allocation</strong> across the field productId, which allows conclusions about the licensed application or component to be drawn. The<strong>license itself</strong> is replaced by a cryptographically unique licenseKey represented, simulated here in the example, by a randomly generated UUID. These two fields are responsible for the formal identification of each license instance.</p><p>Two further fields describe temporal aspects: issuedAt indicates the date and time of issuance, and during validUntil marks the expiry time. This not only allows you to check whether a license is currently valid, but also the period for which it was originally created. Based on this period, the average useful life can be calculated, a central metric type for license analyses in companies.</p><p>The structure is complemented by the field country, which documents the geographical origin or scope of the license. This is relevant for compliance issues, regional licensing models or sales evaluations. The information is intended as an ISO country abbreviation, but can be flexibly adapted to actual requirements.</p><p>Finally, the field isTrial depends on whether it is one<strong>Trial license</strong> or a regular license. This boolean field enables differentiated evaluations according to usage type—an important aspect if, for example, a distinction is to be made between active customers and evaluations.</p><p>The record definition remains deliberately slim, but provides a solid basis for various analysis methods: from counting and grouping to time series and country statistics. Their immutability and precise semantics make them ideal for use in parallel streams and collecting aggregations. In combination with a user-defined collector, the model allows precise and at the same time high-performance evaluation of extensive license stocks, with full parallelizability.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.time.LocalDateTime</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">record</span><span class="nc">LicenseInfo</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">productId</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">licenseKey</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">LocalDateTime</span><span class="w"/><span class="n">issuedAt</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">LocalDateTime</span><span class="w"/><span class="n">validUntil</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">country</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">isTrial</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">)</span><span class="w"/><span class="p">{}</span></span></span></code></pre></div></div><h3 id="the-test-setup">The test setup</h3><p>The structure of the benchmark for license analysis follows a systematic pattern, which is typical for microbenchmark-based performance tests with JMH. The aim is to measure under controlled conditions how the running time of a specially developed collector changes as the amount of data increases, both in serial and in parallel execution. The focus is not on comparing different algorithms, but rather on the stream processing strategy&rsquo;s effect on the aggregation&rsquo;s overall duration.</p><p>The test focuses on the benchmark class LicenseCollectorBenchmark, which is equipped with the annotations of the JMH library. It defines two measuring points: serialCollector() for serial evaluation using stream() and parallelCollector() for parallel evaluation using parallelStream(). Both methods process identical data but use different execution strategies.</p><p>Before each benchmark run, the @Setup(Level.Iteration)-annotated method setUp() generates a consistent test data set. This amount of data – 100,000 randomly generated by default LicenseInfo-Instances - is built fresh with each iteration to eliminate possible caching effects or side effects between benchmarks. The values ​​such as product ID, license key, timestamp, country code and test status are generated randomly, creating realistic heterogeneity.</p><p>Both benchmark methods then fully aggregate the license data, each using the same custom collector. This collector consolidates the data into one final LicenseStats-Object that summarizes several metrics, including the number of valid licenses, the number of test licenses, the average term, the country-specific distribution and the earliest and latest issuance time. This breadth of aggregation ensures that the collector in the benchmark is sufficiently loaded both computationally and in memory terms.</p><p>The actual measurement is carried out via the JMH runtime mechanism with a predefined number of warm-up and measurement cycles. The average execution time per benchmark method is expressed in milliseconds per operation (ms/op), outputting an easily interpretable value representing the total stream processing duration, including accumulation and reduction.</p><p>This setup makes it possible to quantify the efficiency advantages – or potential disadvantages – of parallel stream processing under practical conditions. The comparability between serial and parallel is complete because the data is identical, the collector is similar, and only the execution strategy differs.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import org.openjdk.jmh.annotations.*;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">import java.time.LocalDateTime;</span></span><span class="line"><span class="cl">import java.time.Duration;</span></span><span class="line"><span class="cl">import java.util.*;</span></span><span class="line"><span class="cl">import java.util.concurrent.*;</span></span><span class="line"><span class="cl">import java.util.stream.Collector;</span></span><span class="line"><span class="cl">import java.util.stream.Collectors;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">import static java.util.concurrent.TimeUnit.MILLISECONDS;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">@BenchmarkMode(Mode.AverageTime)</span></span><span class="line"><span class="cl">@OutputTimeUnit(MILLISECONDS)</span></span><span class="line"><span class="cl">@State(Scope.Thread)</span></span><span class="line"><span class="cl">public class LicenseCollectorBenchmark {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> static final int DATA_SIZE = 100_000;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> List<span class="nt">&lt;LicenseInfo&gt;</span> licenseData;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> @Setup(Level.Iteration)</span></span><span class="line"><span class="cl"> public void setUp() {</span></span><span class="line"><span class="cl"> Random rand = new Random();</span></span><span class="line"><span class="cl"> licenseData = new ArrayList<span class="err">&lt;</span>&gt;(DATA_SIZE);</span></span><span class="line"><span class="cl"> for (int i = 0; i<span class="nt">&lt; DATA_SIZE</span><span class="err">;</span><span class="err">i++)</span><span class="err">{</span></span></span><span class="line"><span class="cl"><span class="err">licenseData.add(new</span><span class="err">LicenseInfo(</span></span></span><span class="line"><span class="cl"><span class="err">"product-"</span><span class="err">+</span><span class="err">rand.nextInt(100),</span></span></span><span class="line"><span class="cl"><span class="err">UUID.randomUUID().toString(),</span></span></span><span class="line"><span class="cl"><span class="err">LocalDateTime.now().minusDays(rand.nextInt(1000)),</span></span></span><span class="line"><span class="cl"><span class="err">LocalDateTime.now().plusDays(rand.nextInt(1000)),</span></span></span><span class="line"><span class="cl"><span class="err">"country-"</span><span class="err">+</span><span class="err">rand.nextInt(20),</span></span></span><span class="line"><span class="cl"><span class="err">rand.nextBoolean()</span></span></span><span class="line"><span class="cl"><span class="err">));</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="err">@Benchmark</span></span></span><span class="line"><span class="cl"><span class="err">public</span><span class="err">LicenseStats</span><span class="err">serialCollector()</span><span class="err">{</span></span></span><span class="line"><span class="cl"><span class="err">return</span><span class="err">licenseData.stream()</span></span></span><span class="line"><span class="cl"><span class="err">.collect(LicenseCollectors.stats());</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="err">@Benchmark</span></span></span><span class="line"><span class="cl"><span class="err">public</span><span class="err">LicenseStats</span><span class="err">parallelCollector()</span><span class="err">{</span></span></span><span class="line"><span class="cl"><span class="err">return</span><span class="err">licenseData.parallelStream()</span></span></span><span class="line"><span class="cl"><span class="err">.collect(LicenseCollectors.stats());</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="err">//</span><span class="err">---</span><span class="err">data</span><span class="err">model</span><span class="err">---</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="err">public</span><span class="err">record</span><span class="err">LicenseInfo(</span></span></span><span class="line"><span class="cl"><span class="err">String</span><span class="err">productId,</span></span></span><span class="line"><span class="cl"><span class="err">String</span><span class="err">licenseKey,</span></span></span><span class="line"><span class="cl"><span class="err">LocalDateTime</span><span class="err">issuedAt,</span></span></span><span class="line"><span class="cl"><span class="err">LocalDateTime</span><span class="err">validUntil,</span></span></span><span class="line"><span class="cl"><span class="err">String</span><span class="err">country,</span></span></span><span class="line"><span class="cl"><span class="err">boolean</span><span class="err">isTrial</span></span></span><span class="line"><span class="cl"><span class="err">)</span><span class="err">{}</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="err">public</span><span class="err">record</span><span class="err">LicenseStats(</span></span></span><span class="line"><span class="cl"><span class="err">long</span><span class="err">totalValid,</span></span></span><span class="line"><span class="cl"><span class="err">long</span><span class="err">totalTrial,</span></span></span><span class="line"><span class="cl"><span class="err">double</span><span class="err">avgDurationDays,</span></span></span><span class="line"><span class="cl"><span class="err">Map&lt;String,</span><span class="err">Long</span><span class="nt">&gt;</span> byCountry,</span></span><span class="line"><span class="cl"> LocalDateTime oldestIssued,</span></span><span class="line"><span class="cl"> LocalDateTime newestIssued</span></span><span class="line"><span class="cl"> ) {}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> // --- Collector-Factory ---</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> static class LicenseCollectors {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> static Collector<span class="nt">&lt;LicenseInfo</span><span class="err">,</span><span class="err">?,</span><span class="err">LicenseStats</span><span class="nt">&gt;</span> stats() {</span></span><span class="line"><span class="cl"> class Acc {</span></span><span class="line"><span class="cl"> long valid = 0;</span></span><span class="line"><span class="cl"> long trial = 0;</span></span><span class="line"><span class="cl"> long total = 0;</span></span><span class="line"><span class="cl"> long sumDays = 0;</span></span><span class="line"><span class="cl"> Map<span class="nt">&lt;String</span><span class="err">,</span><span class="err">Long</span><span class="nt">&gt;</span> byCountry = new ConcurrentHashMap<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> LocalDateTime oldest = null;</span></span><span class="line"><span class="cl"> LocalDateTime newest = null;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> void add(LicenseInfo li) {</span></span><span class="line"><span class="cl"> if (li.validUntil().isAfter(LocalDateTime.now())) valid++;</span></span><span class="line"><span class="cl"> if (li.isTrial()) trial++;</span></span><span class="line"><span class="cl"> long days = Duration.between(li.issuedAt(), li.validUntil()).toDays();</span></span><span class="line"><span class="cl"> sumDays += days;</span></span><span class="line"><span class="cl"> total++;</span></span><span class="line"><span class="cl"> byCountry.merge(li.country(), 1L, Long::sum);</span></span><span class="line"><span class="cl"> if (oldest == null || li.issuedAt().isBefore(oldest)) oldest = li.issuedAt();</span></span><span class="line"><span class="cl"> if (newest == null || li.issuedAt().isAfter(newest)) newest = li.issuedAt();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Acc merge(Acc other) {</span></span><span class="line"><span class="cl"> valid += other.valid;</span></span><span class="line"><span class="cl"> trial += other.trial;</span></span><span class="line"><span class="cl"> sumDays += other.sumDays;</span></span><span class="line"><span class="cl"> total += other.total;</span></span><span class="line"><span class="cl"> other.byCountry.forEach((k, v) -&gt; byCountry.merge(k, v, Long::sum));</span></span><span class="line"><span class="cl"> if (oldest == null || (other.oldest != null<span class="err">&amp;&amp;</span> other.oldest.isBefore(oldest)))</span></span><span class="line"><span class="cl"> oldest = other.oldest;</span></span><span class="line"><span class="cl"> if (newest == null || (other.newest != null<span class="err">&amp;&amp;</span> other.newest.isAfter(newest)))</span></span><span class="line"><span class="cl"> newest = other.newest;</span></span><span class="line"><span class="cl"> return this;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> LicenseStats finish() {</span></span><span class="line"><span class="cl"> double avg = total == 0 ? 0 : (double) sumDays / total;</span></span><span class="line"><span class="cl"> return new LicenseStats(valid, trial, avg, byCountry, oldest, newest);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> return Collector.of(</span></span><span class="line"><span class="cl"> Acc::new,</span></span><span class="line"><span class="cl"> Acc::add,</span></span><span class="line"><span class="cl"> Acc::merge,</span></span><span class="line"><span class="cl"> Acc::finish,</span></span><span class="line"><span class="cl"> Collector.Characteristics.UNORDERED,</span></span><span class="line"><span class="cl"> Collector.Characteristics.CONCURRENT</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h2 id="comparison-with-alternative-parallelism-concepts">Comparison with alternative parallelism concepts</h2><p>While parallel streams with collectors offer a declarative and comparatively accessible way to use multi-core processors, there are other parallelisation models in the Java platform, each with its own strengths and areas of application.</p><p>An obvious comparison is with the ForkJoinPool-API. This allows recursive tasks to be broken down and carried out efficiently in parallel, for example, when searching or sorting large amounts of data. However, unlike streams, fork/join structures are explicit: the developer manually controls the division of tasks and the combination of results. This creates more flexibility, but also more complexity and potential for errors.</p><p>Another approach is to use CompletableFuture – especially in combination with methods like supplyAsync() or thenCombineAsync(). The focus here is not on aggregating data but coordinating asynchronous, non-blocking tasks. While streams must have all the available data, they are suitable CompletableFutures for I/O-heavy or delayed processing.</p><p>Newer Java versions also provide structures such as StructuredTaskScope, which comes into play - a still experimental but promising approach to organise parallel tasks better and explicitly control their lifespan. Unlike streams, this API allows structured concurrency with clearly defined error handling and cancellation semantics.</p><p>Parallel collectors position themselves within this spectrum as a compact, declarative mechanism focusing on data-centric, memory-bound processing. They offer low barriers to entry, but are limited in terms of expressiveness and controllability. They reach their natural limits in more complex scenarios - with dependencies between subtasks, adaptive scheduling or concurrent I/O operations.</p><p>Therefore, the choice of the appropriate parallelism model should always be based on the task&rsquo;s requirements: Where pure data aggregation is involved, streams and parallel collectors are ideal. However, where control flow, resilience, or the ability to integrate into distributed systems is required, other APIs are more effective.</p><h2 id="conclusion-and-outlook">Conclusion and outlook</h2><p>Parallel collectors are not a sideshow to the Java Streams API - they are at the heart of whether modern computing can truly scale. Anyone who understands their structure quickly realizes that the correct aggregation in a parallel context is not a coincidence, but the result of precise planning. Associativity, deterministic processing and thread-safe data structures are not academic footnotes, but central pillars of this architecture.</p><p>The concepts shown here—from the structural basis to best practices to your own implementations—are intended not only to convey the functionality but also to create awareness of the responsibility that comes with parallelism. What is harmless in a single-threaded scenario can lead to serious errors in a parallel environment.</p><p>The question for the future is how the Streams API will continue to develop. More structured parallelism models, which are based on concepts such as structured concurrency or data flow graphs, are conceivable. Integration with reactive streams or declarative parallelism à la Fork/Join-DSL would also be a logical next step.</p><p>One thing is certain: Anyone who learns to use parallel collectors correctly and efficiently today will lay the foundation for high-performance and scalable data processing in the Java platform of tomorrow.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Streams</category><media:content url="https://svenruppert.com/images/2025/04/ChatGPT-Image-8.-Apr.-2025-20_17_00-gross.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/04/ChatGPT-Image-8.-Apr.-2025-20_17_00-gross.jpeg"/><enclosure url="https://svenruppert.com/images/2025/04/ChatGPT-Image-8.-Apr.-2025-20_17_00-gross.jpeg" type="image/jpeg" length="0"/></item><item><title>DNS Attacks - Explained</title><link>https://svenruppert.com/posts/dns-attacks-explained/</link><pubDate>Mon, 07 Apr 2025 08:48:06 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/dns-attacks-explained/</guid><description>1. Getting started – trust in everyday internet life Anyone who enters a web address like “www.example.de” into the browser expects a familiar website to appear within seconds. Whether in the home office, at the university, or in the data center, access to online services is now a given. The underlying technical processes are invisible to most users; even in IT practice, they are often taken for granted. One of these invisible processes is name resolution by the Domain Name System (DNS).</description><content:encoded>&lt;![CDATA[<h3 id="1-getting-started--trust-in-everyday-internet-life">1. Getting started – trust in everyday internet life</h3><p>Anyone who enters a web address like “<a href="http://www.beispiel.xn--de-x2t/">www.example.de”</a> into the browser expects a familiar website to appear within seconds. Whether in the home office, at the university, or in the data center, access to online services is now a given. The underlying technical processes are invisible to most users; even in IT practice, they are often taken for granted. One of these invisible processes is name resolution by the Domain Name System (DNS).</p><ol><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#1-getting-started-trust-in-everyday-internet-life">1. Getting started – trust in everyday internet life</a></li><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#2-the-domain-name-system-backbone-of-the-digital-world">2. The Domain Name System – backbone of the digital world</a></li><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#3-invisible-attack-surface-why-dns-is-vulnerable">3. Invisible Attack Surface – Why DNS is vulnerable</a></li><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#4-dns-cache-poisoning-the-classic-attack">4. DNS cache poisoning – The classic attack</a></li><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#5-amplification-and-deception-dns-as-a-vehicle-for-ddos-and-tunneling">5. Amplification and Deception – DNS as a vehicle for DDoS and tunneling</a></li><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#6-dns-hijacking-when-attackers-dictate-the-path">6. DNS Hijacking – When attackers dictate the path</a></li><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#7-attack-detected-but-how">7. Attack detected – but how?</a></li><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#8-protective-measures-think-and-implement-dns-securely">8. Protective measures – think and implement DNS securely</a><ol><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#8-1-general-network-protection-measures">8.1 General Network Protection Measures</a></li><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#8-3-advanced-protection-measures-at-protocol-and-application-level">8.3 Advanced protection measures at protocol and application level</a></li></ol></li><li><a href="https://svenruppert.com/2024/03/22/dns-attacks-explained/#9-conclusion-trust-needs-protection">9. Conclusion – trust needs protection</a></li></ol><p>DNS is the phone book of the Internet. It ensures that human-readable domain names are translated into numeric IP addresses so that computers worldwide can communicate with each other. Without DNS, there would be no URLs, emails, or APIs. Nevertheless, DNS has had a shadowy existence in the security discourse for a long time. It is not a purely technical detail but rather a central link in the trust relationship between the user, application, and infrastructure.</p><p>Because DNS works in the background, it is a popular target for attackers. DNS attacks aim to disrupt, manipulate, or redirect this process for your own purposes, with potentially serious consequences. Anyone who relies on a domain name always leading to the correct IP address risks falling into a well-disguised trap. The affected applications appear to continue to work perfectly—they just communicate with the wrong counterpart.</p><p>This text highlights the diverse forms of attacks on DNS, how they work, and their implications for the operation and development of secure systems. Particular attention is paid to the question of how DNS must be considered from the perspective of modern applications, especially in Java, so as not to become the Achilles heel of an otherwise solid security architecture.</p><h3 id="2-the-domain-name-system--backbone-of-the-digital-world">2. The Domain Name System – backbone of the digital world</h3><p>The Domain Name System has become integral to today&rsquo;s Internet communication. It was developed in the early 1980s to replace the increasingly confusing web of growing hostnames with a scalable, hierarchical structure. Instead of remembering IP addresses, people can now use spoken domain names, and rely on them reliably pointing to the correct servers.</p><p>The basic job of DNS is to translate a string like “<a href="http://www.uni-heidelberg.xn--de-x2t/">www.uni-heidelberg.de“</a> to an IP address like “129.206.100.168”. This process occurs in several steps and is handled by different types of servers. The process usually begins with the so-called resolver – a system service or external service provider that accepts requests from end devices. This resolver in turn queries root name servers, top-level domain servers (e.g. for .de, .com, .org) and finally authoritative name servers until it finds the IP address it is looking for.</p><p>The concept of caching is essential to DNS&rsquo;s efficiency. Each resolver stores already resolved queries for a defined period of time to speed up re-queries. Operating systems and applications can also maintain their own DNS caches. This behavior contributes significantly to reducing global DNS traffic, but it is also one of the biggest weak points, as later chapters will show.</p><p>DNS is based on the User Datagram Protocol (UDP) and is stateless and unencrypted by design. Although extensions such as DNSSEC or DNS-over-HTTPS exist, they are far from being used widely in everyday life. This means that DNS remains an open system, efficient but vulnerable. The assumption that DNS “just works” quickly becomes a dangerous illusion from a security perspective.</p><h3 id="3-invisible-attack-surface--why-dns-is-vulnerable">3. Invisible Attack Surface – Why DNS is vulnerable</h3><p>The architecture of the Domain Name System follows the paradigm of openness. This means that requests are standardised, unencrypted, and mostly stateless, which is precisely why they are particularly high-performance. But the very properties that make DNS a robust part of the Internet infrastructure also pose serious risks. The vulnerability of the DNS is not an accidental by-product of modern forms of attack but is rooted in the original system design.</p><p>DNS was designed when the Internet was a trusted space characterised by academic collaboration and open standards. Security mechanisms were not part of the design goal at the time. Accordingly, DNS still lacks built-in integrity, authenticity, and confidentiality. Queries are not verified, answers are not signed, and the source is not checked. This creates attack vectors that are still being exploited today by actors of all stripes.</p><p>Additionally, DNS requests are typically transmitted over UDP, which makes them easy to forge or intercept. Transaction IDs and ports offer minimal protection against spoofing, but these hurdles are technically easy to overcome with today&rsquo;s means. Even with TCP-based fallbacks, absolute protection is lacking without additional measures such as DNSSEC.</p><p>A particular vulnerability lies in the caching behavior of resolvers and applications. Once saved, answers are considered valid for a certain period, regardless of whether they were correct or manipulated. Attackers can specifically exploit this behavior to inject false answers into the cache and thus redirect many subsequent requests. This so-called cache poisoning is one of the oldest but still most effective attacks on the DNS.</p><p>DNS is therefore not a secure anchor of trust, at least not without additional measures. Anyone using DNS in a security-critical architecture, such as modern Java applications, cloud APIs or container infrastructures, should know the inherent weaknesses. DNS is robust, but it can also be manipulated. This makes it an attractive target – and an often underestimated attack surface.</p><h3 id="4-dns-cache-poisoning--the-classic-attack">4. DNS cache poisoning – The classic attack</h3><p>Among the numerous attack methods on the domain name system, DNS cache poisoning—also known as DNS spoofing—is considered particularly perfidious. The aim of this attack is to manipulate the caching of DNS responses. The attacker injects a fake DNS response into a resolver&rsquo;s cache, with the effect that all subsequent queries to a specific domain name point to a false IP address. The affected users usually do not notice the attack: the website looks as usual, except that the attacker controls it.</p><p>The process of such an attack is as technically interesting as it is security-relevant. The attacker does not wait for a resolver to resolve a specific domain but proactively tries to outsmart the resolver with fake answers. To do this, it must guess the transaction ID of the DNS request - a short, 16-bit random number - and respond faster than the legitimate DNS server. If this succeeds, the resolver accepts the incorrect answer as valid and stores it in the cache.</p><p>What is particularly critical is that this manipulation persists: As long as the cache entry remains valid (TTL), every new request will return the same fake answer. This way, individual users and entire organisations can be systematically misled - for example, on phishing sites, fake authentication services or malware sources. This can result in a REST API being accessed supposedly successfully in Java applications, even though a completely different infrastructure is responding in the background.</p><p>Modern DNS implementations now rely on additional protection mechanisms such as random source ports, rate limiting, or DNSSEC. But many resolvers—especially on local networks or outdated systems—remain vulnerable. Even browsers or libraries with built-in DNS caching can become targets.</p><p>For developers, this means that the trust assumption that InetAddress.getByName() always returns the “correct” address is dangerous. Explicit protective measures are required when DNS is used in security-critical contexts, for example, through the use of cryptographically signed DNS responses (DNSSEC), deliberate cache invalidation, or explicit validation of the remote site, for example, via TLS certificates.</p><p>DNS cache poisoning is a lesson in how a helpful performance optimization—caching—can turn into the exact opposite. Anyone who wants to operate or use DNS securely must understand how the system works and how easily it can be manipulated at its core.</p><h3 id="5-amplification-and-deception--dns-as-a-vehicle-for-ddos-and-tunneling">5. Amplification and Deception – DNS as a vehicle for DDoS and tunneling</h3><p>In addition to the targeted manipulation of DNS caches, there are other attack scenarios in which DNS serves as a technical vehicle for higher-level goals, such as denial-of-service attacks or secret data transmission. Two particularly relevant variants in this context are DNS amplification and DNS tunneling.</p><p>A DNS amplification attack is a form of reflected denial of service (DoS) that exploits the asymmetric structure of the DNS protocol. The attacker sends a small DNS query - often of type ANY - to an open resolver, but spoofs the source address to point to the victim. The resolver responds with a significantly larger amount of data than the request, which is sent directly to the victim. Multiplied across many resolvers, this creates a massive data stream that is explicitly used to overload servers. DNS amplification is so effective because the attacker can cause disproportionate damage with minimal use of resources.</p><p>Another gateway is DNS tunneling. This technique uses DNS packets to pass any other data through firewalls or security measures. Since DNS traffic is hardly restricted in many networks, the protocol is ideal for bypassing control authorities. The payload—such as commands to malware or stolen data—is hidden in seemingly legitimate DNS queries or responses. On the receiving end, this information is extracted and evaluated again. DNS tunneling is considered particularly insidious because it is difficult to detect using traditional security tools.</p><p>A simple Java example demonstrating how a DNS tunnel could be hidden in a seemingly legitimate HTTP communication can be implemented using Java SE&rsquo;s on-board tools. In the following scenario, we implement a minimalist REST server with com.sun.net.httpserver.HttpServer, which receives DNS-like encoded data, representative of a possible C2 channel (Command &amp; Control).</p><p><strong>HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);</strong></p><p><strong>server.createContext("/api/lookup", exchange - &gt; {</strong></p><p>** if (&ldquo;GET&rdquo;.equals(exchange.getRequestMethod())) {**</p><p>** URI requestURI = exchange.getRequestURI();**</p><p>** String query = requestURI.getQuery();**</p><p>** if (query != null &amp;&amp; query.contains(&ldquo;q=&rdquo;)) {**</p><p>** String domainPayload = query.split(&ldquo;q=&rdquo;)[1];**</p><p>** String decoded = domainPayload.replace(".", " &ldquo;); // simplified payload decoding**</p><p>** System.out.println("[DEBUG] Empfangenes DNS-Tunnel-Payload: " + decoded);**</p><p>** }**</p><p>** String response = &ldquo;OK&rdquo;;**</p><p>** exchange.sendResponseHeaders(200, response.length());**</p><p>** exchange.getResponseBody().write(response.getBytes());**</p><p>** exchange.close();**</p><p>** }**</p><p><strong>});</strong></p><p><strong>server.setExecutor(null);</strong></p><p><strong>server.start();</strong></p><p>In this simplified simulation, a DNS-like query parameter is received via a REST interface - e.g. b. http://localhost:8080/api/lookup?q=cmd.run.download. The server “interprets” the dot as a separator of an encoded DNS structure, as is common in DNS tunneling. In a real attack variant, this mechanism would be used by malware or scripts for bidirectional data exchange. What is critical here is that the transport takes place over seemingly unsuspicious channels - HTTP, DNS, ICMP - while the actual payload is hidden deep in protocol structures.</p><p>Both types of attacks have in common that DNS is not a direct target, but rather a tool - a transport system for superimposed attack targets. This results in the need for administrators and developers to monitor DNS traffic functionally and from a security perspective. In Java-based architectures, DNS dependencies should be consciously modeled, logged and – where possible – checked for validation and integrity. A simple forwarding of data via DNS may seem harmless, but it may be the start of a widespread attack.</p><h3 id="6-dns-hijacking--when-attackers-dictate-the-path">6. DNS Hijacking – When attackers dictate the path</h3><p>While DNS cache poisoning focuses on manipulating cached responses, DNS hijacking targets the source itself: the DNS resolver or the infrastructure that controls it. The attack begins when users resolve their domain names - either on the local device, on the home router, on corporate networks, or via an ISP&rsquo;s resolver.</p><p>With DNS hijacking, DNS requests are systematically redirected - for example, through manipulation of network settings, compromised firmware, malicious DHCP servers or malware that specifically changes the configuration of a system. The resolver then no longer returns the IP addresses expected by the user, but instead forwards requests to servers controlled by the attacker. In practice, this means: Even if a user “<a href="http://www.bank.xn--de-x2t/">www.bank.de“</a> enters something in your browser, you don&rsquo;t end up on the real online banking portal, but on a phishing page that looks real.</p><p>This attack is hazardous because it is often difficult to detect. Unlike classic phishing, which relies on fake links or typos, DNS hijacking makes the actual URL appear completely legitimate. The certificate can also appear valid under certain circumstances—for example, with local man-in-the-middle proxies or if the attacker manages to obtain a certificate via compromised certificate authorities.</p><p>Java applications are also potentially affected, primarily if they communicate dynamically with external services. If the host machine&rsquo;s DNS configuration is compromised, all DNS lookups within the JVM are affected, from REST clients to LDAP connections to email components. The application often does not recognize the problem because the connection is technically established correctly, just to the wrong destination.</p><p>The only solution is to take measures that secure DNS on several levels: through fixed resolver configurations, validation of the other side via TLS, use of secure DNS protocols such as DNS-over-HTTPS (DoH) or DNS-over-TLS (DoT), and by monitoring for suspicious name resolutions. In security-critical Java applications, it is recommended to compare the resolved IP addresses with allowlists or even to forego DNS queries if the target systems are known.</p><p>DNS hijacking is a remarkably sophisticated form of deception because it attacks trust where it seems most evident: in the integrity of the technical infrastructure itself.</p><h3 id="7-attack-detected--but-how">7. Attack detected – but how?</h3><p>Detecting DNS attacks is one of the most challenging tasks in network security. This is primarily because DNS is a widespread, dynamic and often invisible protocol. Name resolution occurs in the background, usually without direct user contact, and is often only incompletely recorded in logs and monitoring systems. Accordingly, many DNS-based attacks remain undetected for a long time, mainly when they are carried out precisely and subtly.</p><p>A central problem: Neither the browser nor the application registers whether a DNS response was legitimate, as long as the response is formally correct. A manipulated result usually does not differ from a real one, unless it is actively checked. This is where anomaly detection methods come into play. They try to identify unusual behavior, such as sudden changes in the IP assignment of known domains, an accumulation of DNS responses with short TTL or unusual destination addresses outside of usual networks.</p><p>Intrusion detection systems (IDS) and DNS-specific monitoring solutions such as Zeek, OpenDNS or SecurityOnion can help detect such anomalies. However, the prerequisite is that DNS traffic is completely recorded and evaluated, which brings additional complexity with encrypted DNS (DoH/DoT). In controlled networks, it is therefore recommended to route DNS via dedicated resolvers that can be logged and monitored.</p><p>In Java applications, suspicious name resolutions can be detected by logging IP mappings, comparing against expected hostnames, or active whitelisting. Metrics such as the number of different IP addresses per domain over time or the reuse of certain, unexpected IP ranges are also valuable information.</p><p>In the long term, comprehensive validation of DNS responses via DNSSEC is essential in ensuring integrity. However, DNSSEC is not retroactive - it only protects when domain owners and resolvers actively work together. Accordingly, awareness of DNS attacks and their detection is a crucial component of any security strategy: Anyone who only sees DNS as a technical infrastructure risks being compromised exactly where trust begins - with name resolution.</p><h3 id="8-protective-measures--think-and-implement-dns-securely">8. Protective measures – think and implement DNS securely</h3><h4 id="81-general-network-protection-measures">8.1 General Network Protection Measures</h4><p>Detecting DNS attacks is one of the most challenging tasks in network security. This is primarily because DNS is a widespread, dynamic and often invisible protocol. Name resolution occurs in the background, usually without direct user contact, and is often only incompletely recorded in logs and monitoring systems. Accordingly, many DNS-based attacks remain undetected for a long time, especially when they are carried out specifically and subtly.</p><p>A central problem: Neither the browser nor the application registers whether a DNS response was legitimate, as long as the reaction is formally correct. A manipulated result usually does not differ from a real one, unless it is actively checked. This is where anomaly detection methods come into play. They try to identify unusual behavior, such as sudden changes in the IP assignment of known domains, an accumulation of DNS responses with short TTL or unusual destination addresses outside of usual networks.</p><p>Intrusion detection systems (IDS) and DNS-specific monitoring solutions such as Zeek, OpenDNS or SecurityOnion can help detect such anomalies. However, the prerequisite is that DNS traffic is completely recorded and evaluated, which brings additional complexity with encrypted DNS (DoH/DoT). In controlled networks, it is therefore recommended to route DNS via dedicated resolvers that can be logged and monitored.#### 8.2 Security within Java applications</p><p>In Java applications, suspicious name resolutions can be detected by logging IP mappings, comparing against expected hostnames, or active whitelisting. Metrics such as the number of different IP addresses per domain over time or the reuse of certain, unexpected IP ranges are also valuable information.</p><p>In the long term, comprehensive validation of DNS responses via DNSSEC is essential in ensuring integrity. However, DNSSEC is not retroactive - it only protects when domain owners and resolvers actively work together. Accordingly, awareness of DNS attacks and their detection is a crucial component of any security strategy: Anyone who only sees DNS as a technical infrastructure risks being compromised exactly where trust begins - with name resolution.</p><h4 id="83-advanced-protection-measures-at-protocol-and-application-level">8.3 Advanced protection measures at protocol and application level</h4><p>Various protection measures can be implemented for Java applications to minimize the risks of DNS-based attacks. A key approach is securing the name resolution process within the JVM. The class InetAddress, for example, uses resolvers close to the operating system, which makes the JVM fundamentally vulnerable to manipulation at the system level. In security-critical applications, it can, therefore, make sense to implement your own resolvers with DNSSEC validation or to use external, verified services.</p><p>Additionally, it is recommended to be combined with transport encryption and hostname validation at the application level. The HttpClient from the java.net.http module in Java 11+ enables e.g. B. direct control over TLS certificate checking. By securing via public key pinning, certificate transparency logs or targeted whitelists, manipulated DNS responses can be detected and blocked.</p><p>Another security option is to use alternative DNS protocols such as DoH (DNS over HTTPS) or DoT (DNS over TLS) via upstream proxies. These can also be integrated into containerized environments such as Kubernetes as sidecar resolvers. A locally running DNS proxy (e.g., CoreDNS with DNSSEC and DoH support) can be used as a trustworthy source within the application.</p><p>A practical example of a hedging measure in Java is using your own X509TrustManager to not rely solely on the system-provided chain of trust for the TLS connection. This is particularly useful if you want to counter DNS manipulations that do not break the TLS channel but subvert a false certificate chain - for example, through a compromised CA certificate or a manipulated trust store.</p><p><strong>TrustManager[] trustManagers = new TrustManager[] {</strong></p><p>** new X509TrustManager() {**</p><p>** public X509Certificate[] getAcceptedIssuers() {**</p><p>** return new X509Certificate[0];**</p><p>** }**</p><p>** public void checkClientTrusted(X509Certificate[] certs, String authType) {**</p><p>** // Application-specific testing – e.g. E.g. fingerprinting or pinning**</p><p>** }**</p><p>** public void checkServerTrusted(X509Certificate[] certs, String authType) throws CertificateException {**</p><p>** for (X509Certificate cert : certs) {**</p><p>** cert.checkValidity();**</p><p>** if (!&ldquo;CN=trusted.example.com&rdquo;.equals(cert.getSubjectDN().getName())) {**</p><p>** throw new CertificateException(&ldquo;Untrusted CN: " + cert.getSubjectDN());**</p><p>** }**</p><p>** }**</p><p>** }**</p><p>** }**</p><p><strong>};</strong></p><p><strong>SSLContext sslContext = SSLContext.getInstance(&ldquo;TLS&rdquo;);</strong></p><p><strong>sslContext.init(null, trustManagers, new SecureRandom());</strong></p><p><strong>HttpClient client = HttpClient.newBuilder()</strong></p><p>** .sslContext(sslContext)**</p><p>** .build();**</p><p>This example deliberately checks the subject of the server certificate passed. This makes it possible to ensure that the remote site actually has the expected identity, even if a DNS hijacker manipulates the target system. A more detailed check would be necessary in a productive system, for example, via public key pinning, dedicated trust stores or certificate chain validation.</p><p>Last but not least: DNS requests should be logged, checked for plausibility and ideally compared with an allowlist. This allows a simple but effective protective layer to be implemented, especially in applications where external communication is security-critical.</p><h3 id="9-conclusion--trust-needs-protection">9. Conclusion – trust needs protection</h3><p>Given the central role of the domain name system in virtually every form of modern network communication, it is remarkable how long DNS security played a secondary role in the architecture of many applications. The forms of attack highlighted here - from cache poisoning to DNS hijacking to tunneling and DDoS - clearly show that DNS is much more than a harmless auxiliary component. It is one of the most critical infrastructures of all, and simultaneously one of the most vulnerable.</p><p>Anyone who develops applications today that rely on Internet communication must think about DNS functionally and in terms of security. This applies to web portals, microservices, container environments, and IoT devices. In the Java ecosystem, in particular, the platform from version 11 provides numerous APIs and mechanisms to specifically secure DNS dependencies, be it through explicit TLS configurations, validation of target addresses, logging of suspicious resolutions, or the use of alternative resolvers.</p><p>DNS is not a static problem. The attack vectors continue to develop, and with them, the requirements for protective mechanisms. At a time when trust is becoming a currency of digital interactions, DNS can no longer remain a blind spot. It is time for DNS security to be established as an integral part of every security-conscious software and infrastructure architecture - in the code, operations and all stakeholders&rsquo; minds. logged, checked for plausibility and ideally compared with an allowlist. This allows a simple but effective protective layer to be implemented, especially in applications where external communication is security-critical.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Security</category><media:content url="https://svenruppert.com/images/2024/03/ChatGPT-Image-6.-Apr.-2025-14_07_13-gross.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/03/ChatGPT-Image-6.-Apr.-2025-14_07_13-gross.jpeg"/><enclosure url="https://svenruppert.com/images/2024/03/ChatGPT-Image-6.-Apr.-2025-14_07_13-gross.jpeg" type="image/jpeg" length="0"/></item><item><title>Java Cryptography Architecture (JCA) - An Overview</title><link>https://svenruppert.com/posts/java-cryptography-architecture-jca-an-overview/</link><pubDate>Thu, 03 Apr 2025 12:22:30 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/java-cryptography-architecture-jca-an-overview/</guid><description>The Java Cryptography Architecture (JCA) is an essential framework within the Java platform that provides developers with a flexible and extensible interface for cryptographic operations. It is a central component of the Java Security API and enables platform-independent implementation of security-critical functions.</description><content:encoded>&lt;![CDATA[<p>The<strong>Java Cryptography Architecture (JCA)</strong> is an essential framework within the Java platform that provides developers with a flexible and extensible interface for cryptographic operations. It is a central component of the Java Security API and enables platform-independent implementation of security-critical functions.</p><p>At its core, the JCA provides mechanisms for various cryptographic applications, including the calculation of hashes to ensure data integrity, the generation and verification of digital signatures, and methods for encrypting and decrypting sensitive information. Supporting both symmetrical and asymmetrical encryption methods, it ensures a high level of security when processing data. Cryptographic key management is another key aspect that includes secure storage and exchange of keys.</p><ol><li><a href="https://svenruppert.com/2025/04/03/java-cryptography-architecture-jca-an-overview/#how-is-the-jca-integrated-into-the-security-api">How is the JCA integrated into the Security API?</a></li><li><a href="https://svenruppert.com/2025/04/03/java-cryptography-architecture-jca-an-overview/#symmetric-encryption">Symmetric encryption</a></li><li><a href="https://svenruppert.com/2025/04/03/java-cryptography-architecture-jca-an-overview/#the-first-practical-steps">The first practical steps</a></li><li><a href="https://svenruppert.com/2025/04/03/java-cryptography-architecture-jca-an-overview/#asymmetric-encryption">Asymmetric encryption</a></li><li><a href="https://svenruppert.com/2025/04/03/java-cryptography-architecture-jca-an-overview/#a-practical-example">A practical example</a></li><li><a href="https://svenruppert.com/2025/04/03/java-cryptography-architecture-jca-an-overview/#digital-signatures">Digital signatures</a></li><li><a href="https://svenruppert.com/2025/04/03/java-cryptography-architecture-jca-an-overview/#a-practical-example">A practical example</a></li><li><a href="https://svenruppert.com/2025/04/03/java-cryptography-architecture-jca-an-overview/#now-let-s-combine-symmetric-and-asymmetric-encryption">Now let&rsquo;s combine symmetric and asymmetric encryption</a></li></ol><p>A significant feature of the JCA is the ability to integrate different security providers (so-called providers) that implement different cryptographic algorithms. In addition to the providers that come standard with Java, such as SunJCE, there are alternative implementations, including open source libraries such as Bouncy Castle and hardware-based solutions, that can be integrated via PKCS#11 interfaces.</p><p>The JCA&rsquo;s architecture is characterised by a modular design that allows a separation between the API and the concrete implementations. New cryptographic algorithms can be added via the so-called Service Provider Interface (SPI) without having to adapt existing application code, contributing to the long-term maintainability and security of Java applications.</p><p>In addition to classic cryptography, the JCA also supports mechanisms for the secure generation of random numbers, the calculation of Message Authentication Codes (MACs) to ensure data integrity, and the management of certificates and trust stores. In particular, integration with the Java KeyStore API offers a comprehensive basis for the secure handling of X.509 certificates and cryptographic keys.</p><h2 id="how-is-the-jca-integrated-into-the-security-api">How is the JCA integrated into the Security API?</h2><p>The<strong>Java Security API</strong> is a comprehensive framework for implementing security-related mechanisms in Java applications. It provides various functions that cover various aspects of IT security, including cryptography, authentication, authorization, network security, and cryptographic key management. A central component of this framework is the<strong>Java Cryptography Architecture (JCA).</strong></p><p>While JCA is primarily responsible for the cryptographic functions, it extends the<strong>Java Cryptography Extension (JCE).</strong> This concept includes algorithms for symmetric encryption, key agreement and block cipher modes. In addition to cryptography, the Java Security API includes other security-relevant modules. The<strong>Java Secure Socket Extension (JSSE)</strong> API enables secure communication via network protocols such as TLS and SSL, enabling encrypted connections for web applications and other network services to be realised. This provides authentication and authorisation<strong>Java Authentication and Authorization Service (JAAS)</strong> API mechanisms available to verify user identities and control access rights. This architecture allows integration with various authentication sources such as user-password combinations, Kerberos or biometric methods.</p><p>Another essential component of the framework is the<strong>Java Public Key Infrastructure (PKI) API</strong> , which deals with the management of digital certificates and public keys. This API plays a central role in the realization of TLS encryption and the implementation of digital signatures. Certificate management is carried out via so-called KeyStores, which enable secure storage of cryptographic keys. In addition, the framework supports digital signatures for XML documents through the<strong>Java XML Digital Signature API (XMLSig)</strong> , which is particularly used in web services and SAML authentication scenarios.</p><p>The distinction between the Java Security API and the JCA lies in their respective focus. While the Java Security API provides a comprehensive collection of security-related mechanisms and covers both cryptographic and non-cryptographic security aspects such as authentication, permission management and network security, the JCA focuses<strong>exclusively</strong> on providing cryptographic operations. In this context, JCA offers basic mechanisms for encryption and digital signatures, but JCE is required for modern symmetric encryption methods. Likewise, although JCA includes basic functions for managing cryptographic keys, more comprehensive management of certificates and public key infrastructure requires the use of the PKI API.</p><p>In practice, this means that developers access the JCA and JCE directly to implement cryptographic security mechanisms, while specific modules such as JAAS or JSSE are used for higher security-related functions such as authentication, authorisation or the management of secure network connections. The Java Security API thus forms a coherent but modular security architecture that can be used specifically depending on the application.</p><p>In this article we will only deal with the JCA. The other functional units will be gradually added in later articles.</p><h2 id="symmetric-encryption">Symmetric encryption</h2><p>The symmetric encryption within the<strong>Java Cryptography Architecture (JCA)</strong> offers a powerful and flexible interface for the secure processing of confidential data. It is based on the use of a single secret key for encryption and decryption, which makes it more efficient and faster than asymmetric methods. The JCA provides the Cipher-Class that acts as a central mechanism for symmetric cryptography and enables the implementation of various block and stream ciphers.</p><p>A key feature of symmetric encryption in the JCA is its support for modern block ciphers such as<strong>AES (Advanced Encryption Standard)</strong> , which is considered the current industry standard, as well as older methods such as<strong>DES (Data Encryption Standard)</strong> and its improved version,<strong>Triple DES (3DES)</strong>. The API allows the use of various operating modes, including<strong>ECB (Electronic Codebook)</strong> , which is considered unsafe, as well as safer modes such as<strong>CBC (Cipher Block Chaining)</strong> ,<strong>CFB (Cipher Feedback Mode)</strong> and<strong>GCM (Galois/Counter Mode)</strong>. GCM in particular is preferred because, in addition to confidentiality, it also offers integrity protection through Authenticated Encryption (AEAD).</p><p>A central concept of the JCA is the separation between the abstract API and the concrete implementations provided by various cryptography providers. This keeps the actual implementation interchangeable without tying the application to a specific algorithm or library. The initialization of a Cipher-Object is done by specifying the algorithm as well as the desired operating mode and an optional padding method that ensures block-by-block processing of the data. Common padding schemes are<strong>PKCS5Padding</strong> and<strong>NoPadding</strong> , the latter requiring manual adjustment of the input data.</p><p>The JCA also provides mechanisms for the secure generation and management of keys. About the KeyGenerator-Class, symmetrical keys can be generated based on a defined key length, with key lengths of 128, 192 and 256 bits being particularly common for AES. To ensure the security of the keys, they can be passed through the<strong>Java KeyStore (JKS)</strong> or external hardware security modules (HSMs). Additionally, the API allows keys to be imported and exported in standardised formats to ensure interoperability with other cryptographic systems.</p><p>Another central element of symmetric encryption in the JCA is the treatment of initialisation vectors (IVs), which are crucial for the security of the encryption, especially in modes such as CBC or GCM. IVs must be unique for each encryption operation to prevent attacks such as replay or chosen plaintext attacks. In practice, IVs are often over SecureRandom generated to ensure high entropy and stored or transmitted along with the encrypted data.</p><p>The architecture of the JCA allows developers to use symmetric encryption not only for straightforward file and message encryption, but also in more complex scenarios such as<strong>Transport Layer Security (TLS)</strong> ,<strong>Database encryption</strong> and<strong>Hardware-assisted encryption</strong>. By combining it with other JCA components, such as the<strong>Mac class for Message Authentication Codes (e.g. HMAC-SHA256)</strong> or<strong>Key Derivation Functions (e.g. PBKDF2)</strong> , additional security features such as authentication and key derivation can be implemented.</p><h2 id="the-first-practical-steps">The first practical steps</h2><p>Let&rsquo;s now move on to a simple example of using symmetric encryption within the Java Cryptography Architecture (JCA) and use the AES algorithm family in combination with a secure operating mode and padding. The implementation relies on the Cipher-Class, a central interface in Java for processing cryptographic operations. Choosing secure key management is crucial, which is why the key is stored using the KeyGenerator class, which is created. SecureRandom is used to ensure high entropy and encryption security for the initialisation data.</p><p>Within the method, a character array (char[]) is used instead of a string to ensure that sensitive data does not remain in the string pool and can be specifically overwritten after processing.<strong>(Here, I refer to the previous article, in which I described this in more detail.)</strong> After the key and IV initialisation, encryption is carried out using a Cypher object in AES-CBC mode with PKCS5Padding. The encrypted string is then stored in a byte array. Decryption is done in reverse order by reinitialising the Cipherobject with the same key and IV, which allows the original characters to be restored from the encrypted data stream.</p><p>The following source code demonstrates the implementation of this concept:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javax.crypto.Cipher</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javax.crypto.KeyGenerator</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javax.crypto.SecretKey</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javax.crypto.spec.IvParameterSpec</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.SecureRandom</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.Arrays</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SymmetricEncryptionExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">KeyGenerator</span><span class="w"/><span class="n">keyGenerator</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">KeyGenerator</span><span class="p">.</span><span class="na">getInstance</span><span class="p">(</span><span class="s">"AES"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">keyGenerator</span><span class="p">.</span><span class="na">init</span><span class="p">(</span><span class="n">256</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SecretKey</span><span class="w"/><span class="n">secretKey</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">keyGenerator</span><span class="p">.</span><span class="na">generateKey</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">SecureRandom</span><span class="w"/><span class="n">secureRandom</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecureRandom</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">iv</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">16</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">secureRandom</span><span class="p">.</span><span class="na">nextBytes</span><span class="p">(</span><span class="n">iv</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">IvParameterSpec</span><span class="w"/><span class="n">ivSpec</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IvParameterSpec</span><span class="p">(</span><span class="n">iv</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">originalText</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">{</span><span class="sc">'H'</span><span class="p">,</span><span class="w"/><span class="sc">'e'</span><span class="p">,</span><span class="w"/><span class="err">​​</span><span class="sc">'l'</span><span class="p">,</span><span class="w"/><span class="sc">'l'</span><span class="p">,</span><span class="w"/><span class="sc">'o'</span><span class="p">,</span><span class="w"/><span class="sc">' '</span><span class="p">,</span><span class="w"/><span class="sc">'J'</span><span class="p">,</span><span class="w"/><span class="sc">'C'</span><span class="p">,</span><span class="w"/><span class="sc">'A'</span><span class="p">};</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">encryptedText</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">encrypt</span><span class="p">(</span><span class="n">originalText</span><span class="p">,</span><span class="w"/><span class="n">secretKey</span><span class="p">,</span><span class="w"/><span class="n">ivSpec</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">decryptedText</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">decrypt</span><span class="p">(</span><span class="n">encryptedText</span><span class="p">,</span><span class="w"/><span class="n">secretKey</span><span class="p">,</span><span class="w"/><span class="n">ivSpec</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">decryptedText</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Arrays</span><span class="p">.</span><span class="na">fill</span><span class="p">(</span><span class="n">decryptedText</span><span class="p">,</span><span class="w"/><span class="sc">'\0'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="nf">encrypt</span><span class="p">(</span><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">input</span><span class="p">,</span><span class="w"/><span class="n">SecretKey</span><span class="w"/><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">IvParameterSpec</span><span class="w"/><span class="n">ivSpec</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Cipher</span><span class="w"/><span class="n">cipher</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Cipher</span><span class="p">.</span><span class="na">getInstance</span><span class="p">(</span><span class="s">"AES/CBC/PKCS5Padding"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">cipher</span><span class="p">.</span><span class="na">init</span><span class="p">(</span><span class="n">Cipher</span><span class="p">.</span><span class="na">ENCRYPT_MODE</span><span class="p">,</span><span class="w"/><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">ivSpec</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">cipher</span><span class="p">.</span><span class="na">doFinal</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">String</span><span class="p">(</span><span class="n">input</span><span class="p">).</span><span class="na">getBytes</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="nf">decrypt</span><span class="p">(</span><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">input</span><span class="p">,</span><span class="w"/><span class="n">SecretKey</span><span class="w"/><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">IvParameterSpec</span><span class="w"/><span class="n">ivSpec</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Cipher</span><span class="w"/><span class="n">cipher</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Cipher</span><span class="p">.</span><span class="na">getInstance</span><span class="p">(</span><span class="s">"AES/CBC/PKCS5Padding"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">cipher</span><span class="p">.</span><span class="na">init</span><span class="p">(</span><span class="n">Cipher</span><span class="p">.</span><span class="na">DECRYPT_MODE</span><span class="p">,</span><span class="w"/><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">ivSpec</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">decryptedBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">cipher</span><span class="p">.</span><span class="na">doFinal</span><span class="p">(</span><span class="n">input</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">decryptedChars</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">char</span><span class="o">[</span><span class="n">decryptedBytes</span><span class="p">.</span><span class="na">length</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">decryptedBytes</span><span class="p">.</span><span class="na">length</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">decryptedChars</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">char</span><span class="p">)</span><span class="w"/><span class="n">decryptedBytes</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">decryptedChars</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h2 id="asymmetric-encryption">Asymmetric encryption</h2><p>The<strong>asymmetric encryption</strong> within the Java Cryptography Architecture (JCA) provides a flexible and secure way to encrypt and decrypt data using two different keys: a public key for encryption and a private key for decryption. This method is based on one-way mathematical functions, which make it practically impossible to calculate the private key from the public key. This makes asymmetric cryptography particularly suitable for applications that require secure key distribution and digital signatures, including Transport Layer Security (TLS), Public Key Infrastructure (PKI), and various authentication mechanisms.</p><p>The JCA provides with the Cipherclass provides a central API that enables the implementation of asymmetric encryption methods. The algorithms are particularly widespread<strong>RSA (Rivest-Shamir-Adleman)</strong> ,<strong>Elliptic Curve Cryptography (ECC)</strong> as well as<strong>Diffie-Hellman</strong> for key exchange. RSA is based on the factorization of large prime numbers and is widely used as a classic public key method. In contrast, ECC uses elliptic curves over finite fields and offers comparable security with a smaller key length, making it particularly preferred for resource-constrained environments such as mobile devices or smart cards.</p><p>The key is generated via the KeyPairGenerator-Class that allows to generate asymmetric key pairs with different key lengths. The choice of key length affects security and computing power, with RSA typically operating at 2048 or 4096 bits, while elliptic curves use more efficient 256 or 384 bit keys. The public key generated can be freely distributed, while the private key must be kept secure, for example by storing it in a<strong>Java KeyStore (JKS)</strong> or a hardware security solution such as a Hardware Security Module (HSM).</p><p>A Cipher object is initialised with the desired algorithm and mode for the encryption. The JCA supports various padding mechanisms necessary to adapt input data to the block size of the encryption function used. At RSA,<strong>PKCS1Padding</strong> and<strong>OAEP (Optimal Asymmetric Encryption Padding)</strong> are standard methods, with OAEP being preferred due to its increased security, as it protects against adaptive chosen ciphertext attacks. Due to its high computational complexity, asymmetric encryption is primarily suitable for encrypting small amounts of data, such as key material or hash values, rather than large files or streaming data.</p><p>Another central application area of ​​asymmetric cryptography in the JCA is the digital signature, which is carried out via the Signature class. The private key creates a signature for a message or file, which can later be verified using the public key. Digital signatures ensure both the authenticity and integrity of the transmitted data by ensuring that the message comes from the specified sender and has not been subsequently altered. In particular, algorithms like<strong>RSA with SHA-256</strong> ,<strong>ECDSA (Elliptic Curve Digital Signature Algorithm)</strong> or<strong>EdDSA (Edwards-Curve Digital Signature Algorithm)</strong> are used in modern applications to ensure authenticity and data integrity.</p><p>In addition to classic asymmetric encryption, the JCA also supports hybrid methods in which asymmetric cryptography is used to securely transmit a symmetric key, which is then used for data encryption. This procedure is used, for example, TLS (Transport Layer Security) or PGP (Pretty Good Privacy), to combine the advantages of both types of cryptography: the security of asymmetric key distribution and the efficiency of symmetric encryption.</p><p>The JCA&rsquo;s architecture allows developers to flexibly use asymmetric encryption schemes by supporting a variety of security vendors that provide different implementations. By default, the Java platform offers support for RSA, DSA and elliptic curves, while alternative providers such as<strong>Bouncy Castle</strong> enable additional algorithms and optimisations.</p><h2 id="a-practical-example">A practical example</h2><p>This example uses the RSA algorithm to implement asymmetric encryption, encrypting data with a public key and decrypting it with the corresponding private key. The implementation is done using the Cipher class. The KeyPairGenerator class is used to generate the key pair, which makes it possible to generate an RSA key with a length of 2048 bits. The resulting key pair consists of a public key used for encryption and a private key required for decryption.</p><p>The encryption method takes char[] as input to ensure that no sensitive data is included as String remains in memory. The characters are explicitly converted to a byte array before encryption to enable direct storage operations. Afterwards, Cipherobject is put into encryption mode with the public key, and the conversion is performed. Once encryption is complete, the original byte array is securely overwritten to prevent potential reconstruction in memory.</p><p>Decryption is carried out using an analogous procedure. The Cipher object is initialised with the private key to return the previously encrypted byte array to its original state. After decryption, the byte array turns back into a char[] converted without doing a String conversion. To protect the decrypted data, the original and decrypted byte arrays are overwritten with null values ​​after processing.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javax.crypto.Cipher</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.KeyPair</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.KeyPairGenerator</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.PrivateKey</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.PublicKey</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.Arrays</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">AsymmetricEncryptionExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">KeyPairGenerator</span><span class="w"/><span class="n">keyPairGenerator</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">KeyPairGenerator</span><span class="p">.</span><span class="na">getInstance</span><span class="p">(</span><span class="s">"RSA"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">keyPairGenerator</span><span class="p">.</span><span class="na">initialize</span><span class="p">(</span><span class="n">2048</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">KeyPair</span><span class="w"/><span class="n">keyPair</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">keyPairGenerator</span><span class="p">.</span><span class="na">generateKeyPair</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">PublicKey</span><span class="w"/><span class="n">publicKey</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">keyPair</span><span class="p">.</span><span class="na">getPublic</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">PrivateKey</span><span class="w"/><span class="n">privateKey</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">keyPair</span><span class="p">.</span><span class="na">getPrivate</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">originalText</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">{</span><span class="sc">'H'</span><span class="p">,</span><span class="w"/><span class="sc">'e'</span><span class="p">,</span><span class="w"/><span class="err">​​</span><span class="sc">'l'</span><span class="p">,</span><span class="w"/><span class="sc">'l'</span><span class="p">,</span><span class="w"/><span class="sc">'o'</span><span class="p">,</span><span class="w"/><span class="sc">' '</span><span class="p">,</span><span class="w"/><span class="sc">'J'</span><span class="p">,</span><span class="w"/><span class="sc">'C'</span><span class="p">,</span><span class="w"/><span class="sc">'A'</span><span class="p">};</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">encryptedText</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">encrypt</span><span class="p">(</span><span class="n">originalText</span><span class="p">,</span><span class="w"/><span class="n">publicKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">decryptedText</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">decrypt</span><span class="p">(</span><span class="n">encryptedText</span><span class="p">,</span><span class="w"/><span class="n">privateKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">decryptedText</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Arrays</span><span class="p">.</span><span class="na">fill</span><span class="p">(</span><span class="n">decryptedText</span><span class="p">,</span><span class="w"/><span class="sc">'\0'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Arrays</span><span class="p">.</span><span class="na">fill</span><span class="p">(</span><span class="n">originalText</span><span class="p">,</span><span class="w"/><span class="sc">'\0'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="nf">encrypt</span><span class="p">(</span><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">input</span><span class="p">,</span><span class="w"/><span class="n">PublicKey</span><span class="w"/><span class="n">key</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Cipher</span><span class="w"/><span class="n">cipher</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Cipher</span><span class="p">.</span><span class="na">getInstance</span><span class="p">(</span><span class="s">"RSA/ECB/OAEPWithSHA-256AndMGF1Padding"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">cipher</span><span class="p">.</span><span class="na">init</span><span class="p">(</span><span class="n">Cipher</span><span class="p">.</span><span class="na">ENCRYPT_MODE</span><span class="p">,</span><span class="w"/><span class="n">key</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">inputBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">input</span><span class="p">.</span><span class="na">length</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">input</span><span class="p">.</span><span class="na">length</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputBytes</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">input</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">encryptedBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">cipher</span><span class="p">.</span><span class="na">doFinal</span><span class="p">(</span><span class="n">inputBytes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Arrays</span><span class="p">.</span><span class="na">fill</span><span class="p">(</span><span class="n">inputBytes</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">encryptedBytes</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="nf">decrypt</span><span class="p">(</span><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">input</span><span class="p">,</span><span class="w"/><span class="n">PrivateKey</span><span class="w"/><span class="n">key</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Cipher</span><span class="w"/><span class="n">cipher</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Cipher</span><span class="p">.</span><span class="na">getInstance</span><span class="p">(</span><span class="s">"RSA/ECB/OAEPWithSHA-256AndMGF1Padding"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">cipher</span><span class="p">.</span><span class="na">init</span><span class="p">(</span><span class="n">Cipher</span><span class="p">.</span><span class="na">DECRYPT_MODE</span><span class="p">,</span><span class="w"/><span class="n">key</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">decryptedBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">cipher</span><span class="p">.</span><span class="na">doFinal</span><span class="p">(</span><span class="n">input</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">decryptedChars</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">char</span><span class="o">[</span><span class="n">decryptedBytes</span><span class="p">.</span><span class="na">length</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">decryptedBytes</span><span class="p">.</span><span class="na">length</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">decryptedChars</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">char</span><span class="p">)</span><span class="w"/><span class="n">decryptedBytes</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Arrays</span><span class="p">.</span><span class="na">fill</span><span class="p">(</span><span class="n">decryptedBytes</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">decryptedChars</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h2 id="digital-signatures">Digital signatures</h2><p>Digital signatures are cryptographic mechanisms that ensure digital messages or documents&rsquo; authenticity, integrity and non-repudiation. They are based on asymmetric cryptography and use a key pair consisting of a private key used to generate the signature and a public key used for verification. The central idea behind digital signatures is to confirm the sender&rsquo;s identity and ensure that the transmitted data has not been tampered with during transmission or storage.</p><p>Creating a digital signature begins with calculating a hash value of the message to be signed using a cryptographic hash function. This hash value represents a unique, compact representation of the original message, with even a minimal change to the message resulting in a completely different hash. This hash value is then encrypted with the sender&rsquo;s private key, creating the digital signature. This signature is transmitted or stored along with the original message to ensure its authenticity later.</p><p>The recipient verifies the digital signature using the sender&rsquo;s public key. To do this, the received message is first hashed again to calculate a new hash value. In parallel, the digital signature is decrypted using the public key, restoring the original hash value generated by the sender. If both hash values ​​match, the recipient can assume with a high degree of certainty that the message is authentic and has not been changed. If the hash values ​​are different, it means either that the message has been tampered with or that the signature was created with a different key, indicating an unauthorised modification or a forged identity.</p><p>The security of digital signatures depends largely on the strength of the underlying mathematical procedures and the secure handling of the private keys. Classic signature algorithms like<strong>RSA</strong> or<strong>DSA (Digital Signature Algorithm)</strong> are increasingly being used by more modern processes such as<strong>ECDSA (Elliptic Curve Digital Signature Algorithm)</strong> or<strong>EdDSA (Edwards-Curve Digital Signature Algorithm)</strong> , as they offer comparable security with shorter keys and higher efficiency. The continuous development of cryptographic standards is essential to keep digital signatures secure in the long term against future threats, such as those from quantum computers.</p><h2 id="a-practical-example-1">A practical example</h2><p>This digital signature implementation example demonstrates the creation and verification of a signature using the<strong>RSA signature algorithm with SHA-256</strong>. The Signature-Class carries out the implementation. To generate a valid key pair, the KeyPairGenerator class makes it possible to generate an RSA key pair with a length of 2048 bits. The resulting key pair consists of a private key, which is used to create the signature, and a public key, which is used for later verification.</p><p>Signature generation begins by processing an input known as char[] that is present. Before signing, the characters are converted directly into a byte array without creating strings. The Signature instance is initialised with the private key, after which the data is processed and the signature is generated. This is stored in a separate byte array, while the original input array is overwritten with null values ​​after processing to minimise security risks.</p><p>The signature is verified by initialising the Signature instance again, this time with the public key. The received data is passed to signature verification in byte form, ensuring that it matches the originally signed data. If the verification is successful, it means that the data has remained unchanged and comes from the specified source. On the other hand, a failure of verification indicates that either the data or the signature was manipulated or that an incorrect public key was used for verification.</p><p>The following source code shows an example usage:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.KeyPair</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.KeyPairGenerator</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.PrivateKey</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.PublicKey</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.security.Signature</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.Arrays</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">DigitalSignatureExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">KeyPairGenerator</span><span class="w"/><span class="n">keyPairGenerator</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">KeyPairGenerator</span><span class="p">.</span><span class="na">getInstance</span><span class="p">(</span><span class="s">"RSA"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">keyPairGenerator</span><span class="p">.</span><span class="na">initialize</span><span class="p">(</span><span class="n">2048</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">KeyPair</span><span class="w"/><span class="n">keyPair</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">keyPairGenerator</span><span class="p">.</span><span class="na">generateKeyPair</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">PublicKey</span><span class="w"/><span class="n">publicKey</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">keyPair</span><span class="p">.</span><span class="na">getPublic</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">PrivateKey</span><span class="w"/><span class="n">privateKey</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">keyPair</span><span class="p">.</span><span class="na">getPrivate</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">message</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">{</span><span class="sc">'H'</span><span class="p">,</span><span class="w"/><span class="sc">'e'</span><span class="p">,</span><span class="w"/><span class="sc">'l'</span><span class="p">,</span><span class="w"/><span class="sc">'l'</span><span class="p">,</span><span class="w"/><span class="sc">'o'</span><span class="p">,</span><span class="w"/><span class="sc">' '</span><span class="p">,</span><span class="w"/><span class="sc">'J'</span><span class="p">,</span><span class="w"/><span class="sc">'C'</span><span class="p">,</span><span class="w"/><span class="sc">'A'</span><span class="p">};</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">signature</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">signData</span><span class="p">(</span><span class="n">message</span><span class="p">,</span><span class="w"/><span class="n">privateKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">isValid</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">verifyData</span><span class="p">(</span><span class="n">message</span><span class="p">,</span><span class="w"/><span class="n">signature</span><span class="p">,</span><span class="w"/><span class="n">publicKey</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Signature valid: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">isValid</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Arrays</span><span class="p">.</span><span class="na">fill</span><span class="p">(</span><span class="n">message</span><span class="p">,</span><span class="w"/><span class="sc">'\0'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="nf">signData</span><span class="p">(</span><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">input</span><span class="p">,</span><span class="w"/><span class="n">PrivateKey</span><span class="w"/><span class="n">key</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Signature</span><span class="w"/><span class="n">signature</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Signature</span><span class="p">.</span><span class="na">getInstance</span><span class="p">(</span><span class="s">"SHA256withRSA"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">signature</span><span class="p">.</span><span class="na">initSign</span><span class="p">(</span><span class="n">key</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">inputBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">input</span><span class="p">.</span><span class="na">length</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">input</span><span class="p">.</span><span class="na">length</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputBytes</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">input</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">signature</span><span class="p">.</span><span class="na">update</span><span class="p">(</span><span class="n">inputBytes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">signedBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">signature</span><span class="p">.</span><span class="na">sign</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Arrays</span><span class="p">.</span><span class="na">fill</span><span class="p">(</span><span class="n">inputBytes</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">signedBytes</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">verifyData</span><span class="p">(</span><span class="kt">char</span><span class="o">[]</span><span class="w"/><span class="n">input</span><span class="p">,</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">signatureBytes</span><span class="p">,</span><span class="w"/><span class="n">PublicKey</span><span class="w"/><span class="n">key</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Signature</span><span class="w"/><span class="n">signature</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Signature</span><span class="p">.</span><span class="na">getInstance</span><span class="p">(</span><span class="s">"SHA256withRSA"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">signature</span><span class="p">.</span><span class="na">initVerify</span><span class="p">(</span><span class="n">key</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">inputBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">input</span><span class="p">.</span><span class="na">length</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">input</span><span class="p">.</span><span class="na">length</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputBytes</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">input</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">signature</span><span class="p">.</span><span class="na">update</span><span class="p">(</span><span class="n">inputBytes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">isValid</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">signature</span><span class="p">.</span><span class="na">verify</span><span class="p">(</span><span class="n">signatureBytes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Arrays</span><span class="p">.</span><span class="na">fill</span><span class="p">(</span><span class="n">inputBytes</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">isValid</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h2 id="now-lets-combine-symmetric-and-asymmetric-encryption">Now let&rsquo;s combine symmetric and asymmetric encryption</h2><p>Secure communication between a client and a server can be achieved by combining<strong>asymmetric cryptography for key exchange</strong> and<strong>symmetrical encryption for the actual message transmission</strong>. In this simulation, a hybrid encryption system is implemented that initially uses RSA to enable secure transmission of a session key, which is then used for encrypted communication using AES. This architecture is comparable to the handshake process in<strong>TLS (Transport Layer Security)</strong> , whereby asymmetric cryptography is only used for the initial key exchange. At the same time, the more efficient symmetric encryption secures the messages.</p><p>First, the server generates an<strong>RSA key pair</strong> , which is used to encrypt and decrypt the session key. The client provides the public key while the private key remains on the server. The client then creates one<strong>random AES session key</strong> , which is encrypted with and sent to the server&rsquo;s RSA public key. Once received, the server decrypts the session key using its private key. Both parties now have the same symmetrical key used for subsequent encrypted communication.</p><p>The message transmission takes place using the<strong>AES algorithm in Galois/Counter Mode (GCM)</strong> , which, in addition to confidentiality, also ensures integrity protection through integrated authentication. Each message packet sent is given a random initialisation vector (IV) before encryption to ensure that identical messages do not result in identical ciphertexts.</p><p>If you remember the login process, you can use this procedure to secure the components transmitted over the network, such as hash values ​​and salt values. At this point, I must, of course, point out again that this is a presentation of the basic principles. For productive use, it is recommended to use existing implementations and protocols.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Security</category><category>Uncategorized</category><media:content url="https://svenruppert.com/images/2025/04/ChatGPT-Image-3.-Apr.-2025-12_17_31-gross.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/04/ChatGPT-Image-3.-Apr.-2025-12_17_31-gross.jpeg"/><enclosure url="https://svenruppert.com/images/2025/04/ChatGPT-Image-3.-Apr.-2025-12_17_31-gross.jpeg" type="image/jpeg" length="0"/></item><item><title>Rethinking Java Streams: Gatherer for more control and parallelism</title><link>https://svenruppert.com/posts/rethinking-java-streams-gatherer-for-more-control-and-parallelism/</link><pubDate>Wed, 02 Apr 2025 20:58:21 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/rethinking-java-streams-gatherer-for-more-control-and-parallelism/</guid><description>Since version 8, Java has introduced an elegant, functional approach to processing data sets with the Streams API. The terminal operation collect(&hellip;) represents the bridge from the stream to a targeted aggregation - in the form of lists, maps, strings or more complex data structures. Until Java 20 the processing was done Collector-Instances were regulated, which internally consisted of a supplier, an accumulator, a combiner and optionally a finisher. This model works well for simple accumulations but has noticeable limitations, particularly for complex, stateful, or conditional aggregations.</description><content:encoded>&lt;![CDATA[<p>Since version 8, Java has introduced an elegant, functional approach to processing data sets with the Streams API. The terminal operation collect(&hellip;) represents the bridge from the stream to a targeted aggregation - in the form of lists, maps, strings or more complex data structures. Until Java 20 the processing was done Collector-Instances were regulated, which internally consisted of a supplier, an accumulator, a combiner and optionally a finisher. This model works well for simple accumulations but has noticeable limitations, particularly for complex, stateful, or conditional aggregations.</p><ol><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#the-semantics-of-gatherers">The semantics of gatherers</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#why-gatherers-are-more-than-just-a-better-collector">Why Gatherers are more than just a “better collector”</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#integration-with-streams-api">Integration with Streams API</a><ol><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#sequentieller-gatherer">Sequentieller Gatherer</a><ol><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#initialisation-the-state-of-the-gatherer">Initialisation: The state of the gatherer</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#accumulation-processing-the-input-elements">Accumulation: Processing the input elements</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#emission-the-control-over-the-output">Emission: The control over the output</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#signature-and-purpose-of-the-finisher">Signature and purpose of the finisher</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#concrete-example-chunking-with-remainder">Concrete example: chunking with remainder</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#interaction-with-accumulation">Interaction with accumulation</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#no-return-but-effect-through-push">No return – but effect through push()</a></li></ol></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#parallel-gatherer">Parallel Gatherer</a><ol><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#initializer-the-creation-of-the-accumulator">Initializer – The creation of the accumulator</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#integrator-the-processing-of-elements">Integrator – The processing of elements</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#combiner-the-combination-of-partial-accumulators">Combiner – The combination of partial accumulators</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#finisher-the-transformation-of-the-accumulator-into-the-final-result">Finisher – The transformation of the accumulator into the final result</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#interaction-in-parallel-gatherers">Interaction in parallel gatherers</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#an-example-implementation">An example implementation</a><ol><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#initialiser">Initialiser:</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#integrator">Integrator:</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#combiner">Combiner:</a><ol><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#entries-state1-insert">entries state1 insert</a></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#entries-state2-insert">entries state2 insert</a></li></ol></li><li><a href="https://svenruppert.com/2025/04/02/rethinking-java-streams-gatherer-for-more-control-and-parallelism/#finisher">Finisher:</a></li></ol></li></ol><p>Java 21 was the new interface java.util.stream. The gatherer was introduced, significantly expanding the semantics and control over the accumulation process. A Collector passively collects data, acts as a Gatherer, and actively responds to the incoming elements, which is comparable to a specialised transducer in functional programming languages. Gatherers are particularly useful where procedural or stateful aggregation is necessary, and they also allow element insertion, filtering, skipping, and explicit termination of the gathering process - all within the framework of a functional composable architecture.</p><h2 id="the-semantics-of-gatherers">The semantics of gatherers</h2><p>A Gatherer&lt;T, R&gt; describes the transformation of one Stream<T> into a result of type R under close control over the accumulation process. In contrast to Collector, which is in a sense a container for aggregation logic, the gatherer allows rule-based, state-dependent processing of inputs - including the ability to skip elements (Drop), to insert additionally (Inject) or to end processing early (FinishEarly).</p><p>To make this possible, a gatherer is based on the idea of ​​a<strong>Sink</strong> , which is called in the context of the stream processor. This sink receives every input element, can react to it and thus actively influences the flow of processing. The actual processing is done via a so-called<strong>Adapter Factory</strong> which manages the transitions between the aggregation states.</p><h2 id="why-gatherers-are-more-than-just-a-better-collector">Why Gatherers are more than just a “better collector”</h2><p>While the conventional Collector serves primarily as a final accumulation tool - i.e. to transfer the elements contained in the stream into a target structure such as a list, a map or an aggregation - it works Gatherer conceptually far beyond this role. It is not just an optimised or syntactically varied form of Collectors, but rather an independent mechanism that opens up new expressions for stream processing, both semantically and functionally.</p><p>The central difference lies in the expanded scope for action during the transformation: This means explicitly that a Gatherer can accumulate not only elements but also new, previously non-existent elements that can be fed into the data stream. This opens up the possibility, for example, of introducing initialisation values ​​at the beginning of a stream or placing control characters such as headers and footers specifically at the beginning or end - without artificially expanding the original data stream.</p><p>This creative freedom becomes particularly clear when dealing with conditions. Where a Collector usually operated with a simple accumulator, the state of which leads to a final result, can be a Gatherer work based on state – and allow this state to be influenced across several elements. This opens up new semantic horizons: For example, window operations can be formulated in which temporal or sequential logic is applied - such as aggregating data up to an inevitable &ldquo;end&rdquo; marker, or combining groups of elements that can only be identified by a particular order or content structure.</p><p>Even complex decision structures, such as those required in multi-stage parsing processes or when traversing decision trees, can be achieved using stateful ones that Gatherer implements elegantly and declaratively. The interface remains in the spirit of functional programming: transformation and aggregation can still be described separately, but the Gatherer ensures their connection in a way that was previously only possible through imperative or difficult-to-maintain stream hacks.</p><p>Another advantage is the controlled influence of past elements on current behavior. This is how one can Gatherer For example, making the decision to discard an element because a previous element set a certain context. This context sensitivity capability is particularly relevant in situations where data streams are structurally “not clean” – such as log files, inconsistent data exports, or natural language analysis.</p><p>A Gatherer is not just “better Collector", but a fundamental new tool for data stream-based modeling of complex transformation logic. It opens up a design space in which state, transformation, context and accumulation can interact in a way that was previously only possible with considerable effort - or outside of the stream model. Anyone who has ever worked with stateful Gathererconstructions will notice how much this expands the expressiveness of functional stream architectures.</p><p><strong>A concrete example: grouping with filter logic</strong></p><p>Let&rsquo;s imagine that we want to collect from a stream of strings only those elements that have a particular property and then group them - for example, all words longer than 5 characters, grouped by their first letter. This requirement can be met with a Collector formulate, but requires a combination of preprocessing (e.g. filter(&hellip;)) and downstream grouping. With a Gatherer On the other hand, this combined process can be represented elegantly, comprehensively and in one step:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Gatherer<span class="nt">&lt;String</span><span class="err">,</span><span class="err">?,</span><span class="err">Map&lt;Character,</span><span class="err">List&lt;String</span><span class="nt">&gt;</span>&gt;&gt; gatherer =</span></span><span class="line"><span class="cl"> Gatherer.ofSequential(</span></span><span class="line"><span class="cl"> () -&gt; new HashMap<span class="nt">&lt;Character</span><span class="err">,</span><span class="err">List&lt;String</span><span class="nt">&gt;</span>&gt;(),</span></span><span class="line"><span class="cl"> (map, element, downstream) -&gt; {</span></span><span class="line"><span class="cl"> if (element.length() &gt; 5) {</span></span><span class="line"><span class="cl"> char key = element.charAt(0);</span></span><span class="line"><span class="cl"> map.computeIfAbsent(</span></span><span class="line"><span class="cl">key,</span></span><span class="line"><span class="cl">k -&gt; new ArrayList<span class="err">&lt;</span>&gt;()).add(element);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> return true;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> );</span></span></code></pre></div></div><p>In this example, a decision is made for each element as to whether it will be included in the result. The logic is embedded directly into the Gatherer. The return value accurate signals that processing should continue. You would, at this point, instead of false return, and the stream would end prematurely - a behaviour that is not possible with conventional Collectors is not reachable like that.</p><h2 id="integration-with-streams-api">Integration with Streams API</h2><p>The interface Gatherer&lt;T, A, R&gt; explicitly distinguishes between<strong>more sequential</strong> and<strong>parelleler</strong> processing. The central distinction arises from the factory methods:</p><p><strong>Gatherer.ofSequential(&hellip;) // Can only be used sequentially</strong></p><p><strong>Gatherer.ofConcurrent(&hellip;) // Suitable for parallel streams</strong></p><p>A gatherer who comes with<strong>ofConcurrent(&hellip;)</strong> may be used in parallel streams, but must meet certain requirements: it must be thread-safe or rely on thread-isolated accumulators. This is similar to the logic of parallel collectors, where internal state management allows different elements to be processed simultaneously in independent threads.</p><h3 id="sequentieller-gatherer">Sequentieller Gatherer</h3><p>Especially at<strong>sequential processing</strong> —i.e., if there is no parallelisation—the Gatherer develops its full expressiveness while remaining simple, type-safe, and deterministic.</p><p>The functionality of a sequential gatherer can be divided into three main phases:<strong>initialisation</strong> ,<strong>accumulation</strong> and<strong>Emission</strong>. Each of these phases is described in detail below, with particular attention to the unique features of sequential processing.</p><h4 id="initialisation-the-state-of-the-gatherer">Initialisation: The state of the gatherer</h4><p>Each gatherer has an internal state that is recreated per stream execution. This condition is about one Supplier<S> defined, where S represents the type of condition. In sequential processing, this state is reserved exclusively for a single thread; therefore, no<strong>thread safety requirements exist</strong>. This means that simple objects like ArrayList, StringBuilder, counter arrays, and custom records can be used without problems.</p><p>A typical condition could e.g. B. look like this:</p><p><strong>record ChunkState<T>(List<T> buffer, int chunkSize) {}</strong></p><p>The associated supplier:</p><p><strong>() - &gt; new ChunkState&lt;&gt;(new ArrayList&lt;&gt;(chunkSize), chunkSize)</strong></p><p>The state lives for the entire stream run and serves as context for all further processing steps. Initialisation lays the foundation for state-based logic, such as buffering, counting, aggregation or tracking previous elements.</p><h4 id="accumulation-processing-the-input-elements">Accumulation: Processing the input elements</h4><p>The accumulation function is the heart of every gatherer. It is called for each element of the input stream. The signature of this function is:</p><p><strong>(state, input, downstream) - &gt; { &hellip; }</strong></p><p>This is where the actual logic happens: The input element is brought into the state, and - depending on the current state - a decision can be made as to whether (and if necessary, how many) output values ​​are produced. The decision as to whether an item is passed downstream rests entirely with the gatherer.</p><p><strong>Example:</strong> Every third element of a stream should be emitted.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Gatherer</span><span class="o">&lt;</span><span class="n">String</span><span class="p">,</span><span class="w"/><span class="o">?</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="o">&gt;</span><span class="w"/><span class="nf">everyThird</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Gatherer</span><span class="p">.</span><span class="na">ofSequential</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">int</span><span class="o">[]</span><span class="p">{</span><span class="n">0</span><span class="p">},</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">(</span><span class="n">state</span><span class="p">,</span><span class="w"/><span class="n">element</span><span class="p">,</span><span class="w"/><span class="n">downstream</span><span class="p">)</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">state</span><span class="o">[</span><span class="n">0</span><span class="o">]++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">state</span><span class="o">[</span><span class="n">0</span><span class="o">]</span><span class="w"/><span class="o">%</span><span class="w"/><span class="n">3</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">downstream</span><span class="p">.</span><span class="na">push</span><span class="p">(</span><span class="n">element</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In contrast to classic filter or map operations, this logic is<strong>conditional</strong> and<strong>imperative</strong> : The gatherer remembers how many elements have already been processed and only emits a result for every third. The accumulation logic is, therefore, comparable to that of the accept () Method of a specially defined consumer, but supplemented by downstream control.</p><p><em>Since there is no threading in sequential processing, all operations can be performed directly without synchronisation. The state can be arbitrarily complex and dynamic as long as it is updated correctly within the stream.</em></p><h4 id="emission-the-control-over-the-output">Emission: The control over the output</h4><p>Elements are output via the Sink<R>-Object provided to the Gatherer upon each accumulation. With his method, push(R element) elements can be passed downstream in a targeted and controlled manner. Instead of map or flatMap, where each input leads to one or more outputs<em>automatically</em> transformed, the gatherer decides himself,<strong>if</strong> ,<strong>at</strong> and<strong>was</strong> he emits.</p><p>For example, a gatherer can:</p><ul><li>push individual output values,</li><li>push multiple values ​​at once (e.g. with chunking or tokenisation),</li><li>completely suppress the emission (e.g. under preconditions),</li><li>generate values ​​with a delay (e.g., at the stream&rsquo;s end or after accumulation thresholds).</li><li/></ul><p>Example: Combining three consecutive elements into a string:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Gatherer<span class="nt">&lt;String</span><span class="err">,</span><span class="err">?,</span><span class="err">String</span><span class="nt">&gt;</span> triplets() {</span></span><span class="line"><span class="cl"> return Gatherer.ofSequential(</span></span><span class="line"><span class="cl"> () -&gt; new ArrayList<span class="nt">&lt;String&gt;</span>(3),</span></span><span class="line"><span class="cl"> (state, element, downstream) -&gt; {</span></span><span class="line"><span class="cl"> state.add(element);</span></span><span class="line"><span class="cl"> if (state.size() == 3) {</span></span><span class="line"><span class="cl"> downstream.push(String.join("-", state));</span></span><span class="line"><span class="cl"> state.clear();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> });</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The emission here only occurs when three elements have been collected. These are merged, pushed and the state is then emptied.</p><p>An often overlooked but essential component of a sequential gatherer is the<strong>Finisher</strong> – i.e. the closing function after the last input element. This phase is crucial because often during regular accumulation<strong>Items retained in state</strong> which will only be done at a later date or even<strong>no longer through regular accumulation</strong> can be emitted. The finisher ensures that such remaining elements or aggregated partial results are not lost but are correctly transferred downstream.</p><h4 id="signature-and-purpose-of-the-finisher">Signature and purpose of the finisher</h4><p>The closing function has the signature:</p><p><strong>BiConsumer &lt;State, Sink<R>&gt;</strong></p><p>she will<strong>after all input values ​​have been processed</strong> called by the stream framework – exactly once. In this function, the final state can be accessed and, based on this state, a decision can be made as to whether (additional) output values ​​should be created.</p><p>The finisher is particularly suitable for:</p><ul><li><strong>Partially filled buffers</strong> , for example in chunking operations when the last block does not reach full size,</li><li><strong>Final aggregations</strong> , e.g. B. in averaging, summation, hash calculation or protocol completion,</li><li><strong>Finalisation of state machines</strong> , e.g. B. if an open state still needs to be completed,</li><li><strong>Cleaning or logging</strong> , e.g. B. statistical outputs or final indicators.</li></ul><h4 id="concrete-example-chunking-with-remainder">Concrete example: chunking with remainder</h4><p>Let&rsquo;s look again at the example of a gatherer that groups elements into groups of three. Without finishers, if the number of elements is odd, the last one or two values ​​would be lost:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Gatherer<span class="nt">&lt;String</span><span class="err">,</span><span class="err">?,</span><span class="err">String</span><span class="nt">&gt;</span> triplets() {</span></span><span class="line"><span class="cl"> return Gatherer.ofSequential(</span></span><span class="line"><span class="cl"> () -&gt; new ArrayList<span class="nt">&lt;String&gt;</span>(3),</span></span><span class="line"><span class="cl"> (state, element, downstream) -&gt; {</span></span><span class="line"><span class="cl"> state.add(element);</span></span><span class="line"><span class="cl"> if (state.size() == 3) {</span></span><span class="line"><span class="cl"> downstream.push(String.join("-", state));</span></span><span class="line"><span class="cl"> state.clear();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> },</span></span><span class="line"><span class="cl"> (state, downstream) -&gt; {</span></span><span class="line"><span class="cl"> if (!state.isEmpty()) {</span></span><span class="line"><span class="cl"> downstream.push(String.join("-", state));</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>In the closing function (finish), it is explicitly checked whether there are still elements in the state—i.e., whether the buffer is incomplete. These residual values ​​are then combined into an aggregate and pushed.</p><p>Without the finisher there would be the gatherer<em>functionally incomplete</em> : For input sets that are not a multiple of three, the last chunk would simply be discarded - a classic off-by-one error.</p><h4 id="interaction-with-accumulation">Interaction with accumulation</h4><p>The finisher is<strong>semantically separated</strong> from the accumulation logic, but accesses the same state. This means that, depending on the specific application, it can use the same auxiliary functions or serialisation routines as the accumulation itself. In practice, it is advisable to define auxiliary methods for common logic such as &ldquo;combine and empty the list&rdquo; in order to avoid redundancy.</p><h4 id="no-return--but-effect-through-push">No return – but effect through push()</h4><p>The finisher<strong>gives no return value</strong> back, but - like the accumulation function - works via what is provided Sink. So it doesn&rsquo;t find any return semantics, instead a controlled completion of processing push() views.</p><p>The finisher of a sequential gatherer is the<strong>binding conclusion</strong> of the processing model. He guarantees that<strong>all information remaining in the state</strong> is processed and, if necessary, emitted. Especially in data stream-based applications where incomplete blocks, open ends, or residual states are typical, the finisher is essential to avoid data loss and ensure semantic correctness. Therefore, the finisher has a clean gatherer design<strong>that is not optional</strong> but rather an integral part of a well-defined stream processing step.</p><p>A sequential gatherer combines:</p><ul><li>the state handling of an aggregator,</li><li>the control flow options of a parser,</li><li>the expressiveness of an imperative processor,</li><li>and the clarity of functional APIs.</li></ul><p>By foregoing parallelisation logic and concurrency, the sequential variant allows a gatherer to be developed with minimal overhead and maximum expressiveness - a tool that combines both the flexibility of imperative logic and the composition of functional programming.</p><h3 id="parallel-gatherer">Parallel Gatherer</h3><p>A parallel gatherer is for<em>parallel</em> Data processing pipelines are responsible for the four phases initialiser, integrator, combiner and finisher can be explicitly separated and controlled from each other.</p><h4 id="initializer--the-creation-of-the-accumulator">Initializer – The creation of the accumulator</h4><p>The method initialiser defines how a new accumulator (internal state) is created. This is the first step in processing each substream in sequential and parallel pipelines.</p><p>The signature is also:<strong>Supplier<A> initializer();</strong></p><p>In parallel processing, this initialiser is called several times - once per substream, i.e. per thread that takes over a split of the data. This ensures that no synchronisation within the accumulator is necessary: ​​each thread operates in isolation with its own state.</p><h4 id="integrator--the-processing-of-elements">Integrator – The processing of elements</h4><p>The integrator is a central function for inserting stream elements into the accumulator. It is one BiConsumer&lt;A, T&gt;, i.e. a function for each element T the Accumulator A changed accordingly.</p><p>The signature reads:<strong>BiConsumer &lt;A, ? super T&gt; integrator();</strong></p><p>In parallel streams, this integrator is also applied to partial accumulators. What is important here is that this function may only change the accumulator locally and may not influence any global states.</p><h4 id="combiner--the-combination-of-partial-accumulators">Combiner – The combination of partial accumulators</h4><p>The combiner&rsquo;s task is to combine several independently processed accumulators into one overall accumulator. This phase is only relevant in parallel stream executions. The combiner receives two partial results—typically from two threads—and has to combine them into a common result.</p><p>The signature is:<strong>BinaryOperator<A> combiner();</strong></p><p>The correct implementation of the combiner is essential for the correctness of the parallel execution. It must be associative. This is the only way the JVM can freely distribute the operation across multiple threads.</p><h4 id="finisher--the-transformation-of-the-accumulator-into-the-final-result">Finisher – The transformation of the accumulator into the final result</h4><p>The finisherfunction transforms the accumulator A into the desired result R. While A is used internally to work efficiently during aggregation R the actual result of the entire operation - such as an immutable collection, a merged string, an optional, a report object, etc.</p><p>The signature reads:<strong>Function &lt;A, R&gt; finisher();</strong></p><p>Unlike the integrator and combiner, the finisher becomes accurate<strong>once</strong> called, at the end of the entire processing chain. It therefore serves as a bridge between the internal aggregation mechanism and external representation.</p><h4 id="interaction-in-parallel-gatherers">Interaction in parallel gatherers</h4><p>In a parallel stream with gatherer-based collector, the following happens:</p><ol><li>An initialiser is called for each partial stream (split) to create a local accumulator.</li><li>The integrator processes all elements in the respective substream one after the other and modifies the local accumulator.</li><li>The combiner phase is called as soon as two partial accumulators need to be combined. This happens either through ForkJoin merging or in the final reduction step.</li><li>After all partial accumulators have been combined, the finisher is called to calculate the final result.</li></ol><p>This explicit separation and the ability to control each phase make Gatherer a powerful tool for complex, stateful, or stateless aggregations—especially when performance through parallelisation is crucial or custom aggregation logic is required.</p><h2 id="an-example-implementation">An example implementation</h2><p>Let&rsquo;s first define the gatherer in general and put it in a stream.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">Gatherer<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">?,</span><span class="err">ConcurrentMap&lt;Integer,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt;&gt; concurrentGatherer =</span></span><span class="line"><span class="cl"> Gatherer.of(</span></span><span class="line"><span class="cl"> initializer,</span></span><span class="line"><span class="cl"> integrator,</span></span><span class="line"><span class="cl"> combiner,</span></span><span class="line"><span class="cl"> finisher</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">IntStream.rangeClosed(0, END_INCLUSIVE)</span></span><span class="line"><span class="cl"> .boxed()</span></span><span class="line"><span class="cl"> .parallel()</span></span><span class="line"><span class="cl"> .gather(concurrentGatherer)</span></span><span class="line"><span class="cl"> .forEach(e -&gt; System.out.println("e.size() = " + e.size()));</span></span></code></pre></div></div><p>Now we define the respective subcomponents:</p><h3 id="initialiser">Initialiser:</h3><p>var initializer = (Supplier &lt;ConcurrentMap&lt;Integer, List<Integer>&raquo;) ConcurrentHashMap::new;</p><h3 id="integrator">Integrator:</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">var integrator = new Gatherer.Integrator<span class="nt">&lt;</span></span></span><span class="line"><span class="cl"><span class="nt"> ConcurrentMap</span><span class="err">&lt;Integer,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt;,</span></span><span class="line"><span class="cl"> Integer,</span></span><span class="line"><span class="cl"> ConcurrentMap<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt;&gt;() {</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public boolean integrate(ConcurrentMap<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt; state,</span></span><span class="line"><span class="cl"> Integer element,</span></span><span class="line"><span class="cl"> Gatherer.Downstream<span class="err">&lt;</span></span></span><span class="line"><span class="cl"> ? super ConcurrentMap<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt;&gt; downstream) {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> if (element &gt; END_INCLUSIVE) return false; //processing can be interrupted</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> int blockStart = (element / 100) * 100;</span></span><span class="line"><span class="cl"> state</span></span><span class="line"><span class="cl"> .computeIfAbsent(blockStart, k -&gt; Collections.synchronizedList(new ArrayList<span class="err">&lt;</span>&gt;()))</span></span><span class="line"><span class="cl"> .add(element);</span></span><span class="line"><span class="cl"> return true;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">};</span></span></code></pre></div></div><p>The integrator is responsible for processing individual stream elements (here: Integer) and their insertion into a shared state (ConcurrentMap). The element is sorted according to a specific grouping criterion: all elements that are in the same block of 100 (e.g. 0–99, 100–199, &hellip;), are entered in the same list.</p><p>There is a special feature in this implementation:</p><p><strong>if (element &gt; END_INCLUSIVE) return false;</strong></p><p>This condition serves as<strong>Abort signal</strong> : as soon as an element is above a specified limit (END_INCLUSIVE), processing is completed by returning false canceled. This is a special feature of the Gatherer-Model: The return value false signals that no further elements should be processed - a type<em>early termination</em>.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">state</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">computeIfAbsent</span><span class="p">(</span><span class="n">blockStart</span><span class="p">,</span><span class="w"/><span class="n">k</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">Collections</span><span class="p">.</span><span class="na">synchronizedList</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">()))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">element</span><span class="p">);</span></span></span></code></pre></div></div><p>This line does the actual grouping: If under the key blockStart, if no list already exists, a new, synchronised one will be created. ArrayList created.</p><p>The current item is then added to this list.</p><p>By using Collections.synchronizedList(&hellip;) becomes<strong>even within a parallel gatherer context</strong> ensures that list accesses are thread-safe - even though the ConcurrentMap itself is only responsible for map access, not for the values ​​it contains.</p><p>The integrator therefore defines the following processing semantics:</p><ul><li>Elements are grouped by blocks of 100 (0–99, 100–199, etc.).</li><li>The assignment is done via a ConcurrentMap, where each block contains a list.</li><li>The lists themselves are synchronised to allow concurrency within the list operations.</li><li>By returning false can processing<strong>ended early</strong> become – e.g. B. when a limit value is reached.</li><li/></ul><h3 id="combiner">Combiner:</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">var combiner = new BinaryOperator<span class="nt">&lt;ConcurrentMap</span><span class="err">&lt;Integer,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt;&gt;() {</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public ConcurrentMap<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt;</span></span><span class="line"><span class="cl"> apply(ConcurrentMap<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt; state1,</span></span><span class="line"><span class="cl"> ConcurrentMap<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt; state2) {</span></span><span class="line"><span class="cl"> var mergedMap = new ConcurrentHashMap<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt;();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> // fill in state1</span></span><span class="line"><span class="cl"> state1.forEach((key, value) -&gt;</span></span><span class="line"><span class="cl"> mergedMap.merge(key, value, (v1, v2) -&gt; {</span></span><span class="line"><span class="cl"> v1.addAll(v2);</span></span><span class="line"><span class="cl"> return v1;</span></span><span class="line"><span class="cl"> })</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> // fill in state 2</span></span><span class="line"><span class="cl"> state2.forEach((key, value) -&gt;</span></span><span class="line"><span class="cl"> mergedMap.merge(key, value, (v1, v2) -&gt; {</span></span><span class="line"><span class="cl"> v1.addAll(v2);</span></span><span class="line"><span class="cl"> return v1;</span></span><span class="line"><span class="cl"> })</span></span><span class="line"><span class="cl"> );</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> return mergedMap;</span></span><span class="line"><span class="cl"> }</span></span></code></pre></div></div><p>First, a new empty, thread-safe map is prepared to contain all entries state1 and state2 should be inserted. This new map is deliberately fresh because neither state1 still state2 should be changed - this protects against unwanted side effects and makes the function work<strong>referentially transparent</strong>.</p><p><strong>var mergedMap = new ConcurrentHashMap &lt;Integer, List<Integer>&gt;();</strong></p><h4 id="entries-state1-insert">entries state1 insert</h4><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">state1</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">forEach</span><span class="p">((</span><span class="n">k</span><span class="p">,</span><span class="n">v</span><span class="p">)</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">mergedMap</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">computeIfAbsent</span><span class="p">(</span><span class="n">k</span><span class="p">,</span><span class="w"/><span class="n">k1</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">())</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addAll</span><span class="p">(</span><span class="n">v</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">});</span></span></span></code></pre></div></div><p>This method<strong>computeIfAbsent</strong> checks whether in the target map mergedMap already an entry for the key k exists. If this is not the case, the lambda is used k1 -&gt; new ArrayList&lt;&gt;() a new entry is created and inserted. The method guarantees that<strong>an existing, modifiable list</strong> is returned afterwards - regardless of whether it was just created or already existed.</p><p>The method addAll(&hellip;) hangs all elements of the list v out of state1 to the corresponding list in mergedMap to. This expands the aggregate state for this key in the target map.</p><h4 id="entries-state2-insert">entries state2 insert</h4><p>The same process is then repeated for state2 repeated:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">state2</span><span class="p">.</span><span class="na">forEach</span><span class="p">((</span><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">value</span><span class="p">)</span><span class="w"/><span class="o">-&gt;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">mergedMap</span><span class="p">.</span><span class="na">merge</span><span class="p">(</span><span class="n">key</span><span class="p">,</span><span class="w"/><span class="n">value</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="n">v1</span><span class="p">,</span><span class="w"/><span class="n">v2</span><span class="p">)</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">v1</span><span class="p">.</span><span class="na">addAll</span><span class="p">(</span><span class="n">v2</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">v1</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">})</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">);</span></span></span></code></pre></div></div><p>Every entry is made here state2 in the mergedMap transmitted. If the key does not yet exist, the value (value, one List<Integer>) taken directly. If the key is already in mergedMap, it exists by merge(&hellip;) using a custom merge strategy: the list contents of both maps are merged v1.addAll(v2) combined. What is important here is that v1 is the existing list, and v2 is the newly added one.</p><p>In the end, the newly created, combined map is returned—it represents the complete aggregate state, which contains the contents of both partial states.</p><h3 id="finisher">Finisher:</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">var finisher = new BiConsumer<span class="nt">&lt;</span></span></span><span class="line"><span class="cl"><span class="nt"> ConcurrentMap</span><span class="err">&lt;Integer,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt;,</span></span><span class="line"><span class="cl"> Gatherer.Downstream<span class="err">&lt;</span>? super ConcurrentMap<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt;&gt;&gt;() {</span></span><span class="line"><span class="cl"> @Override</span></span><span class="line"><span class="cl"> public void accept(</span></span><span class="line"><span class="cl"> ConcurrentMap<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt; state,</span></span><span class="line"><span class="cl"> Gatherer.Downstream<span class="err">&lt;</span>? super ConcurrentMap<span class="nt">&lt;Integer</span><span class="err">,</span><span class="err">List&lt;Integer</span><span class="nt">&gt;</span>&gt;&gt; downstream) {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> downstream.push(state);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">};</span></span></code></pre></div></div><p>This implementation of the finisher is minimalistic but functionally correct: it takes the final state (state) of the accumulator - one ConcurrentMap&lt;Integer, List<Integer>&gt; – and hands it directly to him<strong>downstream</strong> , i.e. the next stage of the processing chain in the stream.</p><p>The attribute<strong>state</strong> is the aggregated result of the previous steps (initialiser, integrator, combiner). In this case it is a map that maps blocks of 100 (Integers) to a list of the associated values ​​(List<Integer>).</p><p>The attribute<strong>downstream</strong> is a push receiver that consumes the end result. It abstracts the next processing stage, such as a downstream map, flatMap, or terminal collection process.</p><p>The method push(&hellip;) of the downstream object explicitly forwards the finished result to the next processing stage. This is fundamentally different from conventional collector Concepts, where the end result is simply returned.</p><p>This type of handover makes it possible, in particular, in one<strong>within Gatherer defined, stateful context</strong> to deliver multiple or even conditional results – for example:</p><ul><li><strong>Streaming</strong> of intermediate results (e.g. after a specific batch)</li><li><strong>Quitting early</strong> after the first valid result</li><li><strong>Multiple edition</strong> during partitioning</li><li/></ul><p>In this specific case, however,<strong>precisely one result</strong> was passed on—the fully aggregated map. This classic “push-forward finisher” determines the condition as a result<em>emitted</em>.</p><p>We have now examined the Gatherer in detail and pointed out the differences in sequential and parallel processing. So, everything should be together for your first experiments.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Concurrency</category><category>Java</category><category>Streams</category><media:content url="https://svenruppert.com/images/2025/04/ChatGPT-Image-2.-Apr.-2025-20_53_56-gross.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/04/ChatGPT-Image-2.-Apr.-2025-20_53_56-gross.jpeg"/><enclosure url="https://svenruppert.com/images/2025/04/ChatGPT-Image-2.-Apr.-2025-20_53_56-gross.jpeg" type="image/jpeg" length="0"/></item><item><title>From Java 8 to 24: The evolution of the Streams API</title><link>https://svenruppert.com/posts/from-java-8-to-24-the-evolution-of-the-streams-api/</link><pubDate>Sat, 29 Mar 2025 13:08:23 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/from-java-8-to-24-the-evolution-of-the-streams-api/</guid><description>The introduction of the Stream API in Java marked a crucial step in the development of functional programming paradigms within the language. With Java 24, stream processing has been further consolidated and is now a central tool for declarative data processing Stream not about an alternative form of Collection, but rather an abstract concept that describes a potentially infinite sequence of data that is transformed and consumed through a pipeline of operations. While a Collection is a data structure that stores data, Stream is a carrier of a computing model: It does not store any data but instead allows the description of data flows.</description><content:encoded>&lt;![CDATA[<p>The introduction of the Stream API in Java marked a crucial step in the development of functional programming paradigms within the language. With Java 24, stream processing has been further consolidated and is now a central tool for declarative data processing<em>Stream</em> not about an alternative form of Collection, but rather an abstract concept that describes a potentially infinite sequence of data that is transformed and consumed through a pipeline of operations. While a Collection is a data structure that stores data, Stream is a carrier of a computing model: It does not store any data but instead allows the description of data flows.</p><ol><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#getting-started-with-streams-in-java">Getting started with streams in Java</a></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#the-power-of-composition">The power of composition</a></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#effects-on-the-classic-design-patterns-in-java">Effects on the Classic Design Patterns in Java</a></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#an-example-from-classic-java-to-the-use-of-streams">An example from classic Java to the use of streams</a><ol><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#example-in-classic-java-without-streams">Example in classic Java without streams</a></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#example-in-java-using-streams-api">Example in Java using Streams API</a></li></ol></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#changes-to-the-streams-api-from-java-8-to-21-an-overview">Changes to the Streams API from Java 8 to 21 - an overview</a></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#a-concrete-example-of-flatmap-and-mapmulti">A concrete example of flatMap and mapMulti</a><ol><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#conceptual-difference">Conceptual difference</a></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#example-with-flatmap">Example with flatMap</a></li></ol></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#what-s-new-with-java-24-gatherer">What&rsquo;s new with Java 24 - Gatherer</a></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#practical-examples-with-the-gatherer">Practical examples with the Gatherer</a><ol><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#windowfixed">windowFixed</a></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#windowsliding">windowSliding</a></li><li><a href="https://svenruppert.com/2025/03/29/from-java-8-to-24-the-evolution-of-the-streams-api/#possible-optimizations">possible optimizations</a></li></ol></li></ol><p>Each stream processing can be divided into three logically separate phases. The starting point is always a source - one Collection, an array, an I/O channel or a created structure like Stream.generate(&hellip;) be. The source is followed by a number of<em>Intermediate Operations</em> , which transform, filter or sort the Stream. These operations are<em>lazy</em> , d. h. they are not executed immediately but are merely registered. Only through a final<em>Terminal Operation</em> , such as forEach, collect or reduce, execution is triggered and the entire pipeline is concretized. The<em>Laziness principle</em> allows the JVM to optimize, merge, or even eliminate operations without impacting the final result.</p><p>A fundamental feature of stream architecture is its uniqueness. A stream can be consumed exactly once. Once a terminal operation has been performed, the Stream is exhausted. This has profound implications for the design of stream-based algorithms, as one must carefully decide when and where to evaluate a pipeline. Compared to reusable data containers like lists, this requires a shift towards fluent, targeted processing.</p><p>The structure of a stream pipeline follows a well-defined structure. It begins with the definition of the data source, is completed by a sequence of intermediate operations and ends with a final consumption action. Under the hood, the JVM detects and analyzes this structure and performs a variety of optimizations. For example, the JVM can do operations like map and filter combine (Operation Fusion), activate parallel execution on suitable sources or avoid redundant calculations. These optimization options are only possible due to the declarative nature of streams and underline the paradigmatic difference to imperative code with classic loops.</p><p>In Java, the Stream API remains a key element for modern, expressive, and concurrent computing. However, understanding them requires a deep awareness of the differences from the conventional collection hierarchy - particularly in terms of laziness, single use, and JVM-powered optimizations that make streams a powerful tool in the Java developer&rsquo;s repertoire.</p><h2 id="getting-started-with-streams-in-java">Getting started with streams in Java</h2><p>Getting started with the Stream API in Java requires a basic understanding of functional programming concepts and the way Java integrates these concepts within an object-oriented language. Streams make it possible to describe data flows declaratively, with the focus not on the<em>How</em> but on the<em>Where</em> the data processing lies. This shift from imperative to declarative thinking forms the foundation for a modern, concise and, at the same time, expressive programming model.</p><p>A typical entry point into stream processing is the conversion of existing data structures, such as List- or Setinstances, into a stream. This is done using the method stream(), by most Collection implementations is provided. Alternatively, methods such as: Stream.of(&hellip;), Arrays.stream(&hellip;) or IntStream.range(&hellip;) can be used to create stream instances. Regardless of the source, this always creates a pipeline that is potentially evaluated with a delay.</p><p>After a stream is initialized, intermediate operations such as filter, map, or sorted are applied to transform or refine the data flow. These operations are purely descriptive and do not modify the original data source or trigger any calculation. Instead, you build a kind of recipe that is later executed through a terminal operation.</p><p>As mentioned before, the pipeline is completed by a terminal operation that triggers execution and delivers the result material. Only at this point is the pipeline evaluated, with all previously defined steps being applied to the data in one pass. This is a key difference from traditional processing using loops or iterators, where each processing step is executed immediately.</p><p>Especially at the beginning, it is helpful to experiment with simple examples, such as filtering character strings or transforming integers. This allows an intuitive understanding of the data flow, the chaining of operations and the underlying execution logic. The advantage of streams quickly becomes apparent: they promote a concise, readable and at the same time efficient expression for data-driven operations, without unnecessarily complicating the structure of the underlying code.</p><h2 id="the-power-of-composition">The power of composition</h2><p>The Java Stream API&rsquo;s strength lies in its ability to process data declaratively and, above all, in its ability to form complex processing steps into a compositional unit. Streams allow transformation and filter logic to be defined modularly and combined elegantly, making even complex data flows comprehensible and reusable. The underlying idea is to understand arithmetic operations as functional units that can be fluidly combined into a processing chain.</p><p>From a software architecture perspective, stream composition opens an elegant way to modularize processing steps. For example, methods can be defined as those that encapsulate a specific stream transformation and can be embedded as a building block in a more extensive pipeline. By using higher-order functions, for example, by returning a Function&lt;T, R&gt;-Object, a repertoire of reusable processing modules is created that can be put together dynamically. This not only promotes readability, but also testability and maintainability of the code.</p><p>Another advantage is the clear separation of structure and behaviour. While imperative code typically mixes loop logic with specialized logic, stream composition allows a decoupled view: the type of data flow (e.g. sequential or parallel) is orthogonal to the question of what happens to the data in terms of content. This separation makes it easier to reuse or specifically expand existing pipelines in different contexts without fundamentally changing their structure.</p><p>Last but not least, the combination of different stream types expresses this compositional ability. So primitive streams (IntStream, LongStream, DoubleStream) with object streams via conversion methods like boxed() or mapToInt() be connected. The same can be said about flatMap, which realizes the processing of nested data structures, whereby a stream of containers is created into a flat data stream that can be processed further seamlessly.</p><p>Therefore, the ability to compose streams is more than just a syntactic convenience. It represents a fundamental paradigm shift in the way data flows are designed, structured, and executed in Java. Anyone who masters this approach can convert even complex application logic into clearly structured, functional modules—a gain in terms of both maintainability and expressiveness.</p><h2 id="effects-on-the-classic-design-patterns-in-java">Effects on the Classic Design Patterns in Java</h2><p>The integration of the Stream API into Java has profound implications for the use and necessity of classic design patterns. Many patterns that emerged in object-oriented development emerged in response to the lack of functional means of expression. However, with the emergence of streams and their close integration with functional concepts such as lambdas and higher-order functions, the role of these patterns is fundamentally changing. This is particularly noticeable with patterns that were initially used to control the iteration, transformation or filtering of data structures.</p><p>A prominent example of this is the<em>Iterator Pattern</em> , which has been primarily made obsolete by streams. While the Iterator Pattern in classic Java was considered an idiomatic approach to processing collections sequentially, the Stream API completely encapsulates this responsibility. Access to the elements is implicit and declarative; the stream configuration controls the order and traversal behaviour. Manual control over iteration, as provided by the iterator pattern, is thus replaced by a more expressive and, at the same time, less error-prone abstraction.</p><p>That too<em>Strategy Pattern</em> undergoes a transformation through streams. Strategies that were previously coded as separate classes or interfaces with concrete implementations can now be elegantly implemented via lambdas and method-based composition within stream pipelines. Filtering or mapping strategies that were previously modelled through object-oriented inheritance hierarchies can now be defined inline and dynamically combined - a change that not only simplifies the source code but also increases its flexibility.</p><p>The<em>Decorator Pattern</em> , traditionally used to extend functionality through nested object structures dynamically, finds a modern equivalent in the stream world in the chaining of intermediate operations. Each operation transforms the Stream and adds another processing layer - not through inheritance or object composition, but through functional pipeline elements. The resulting structure is more compact and allows for a much more dynamic configuration at runtime.</p><p>Furthermore, stream architecture sheds new light on the<em>Template Method Pattern</em>. While this was initially used to define the structure of an algorithm and implement varying steps in subclasses, the Stream API allows such &ldquo;algorithms&rdquo; to be defined via methodically combined functions. The sequence of stream operations specifies the fixed processing steps, while passed functions specify concrete steps. This makes the behaviour more modular and decoupled from static inheritance.</p><p>Finally, streams also change the understanding of patterns like<em>Chain of Responsibility</em> or<em>Pipeline</em>. These patterns are designed to model flexible processing sequences in which each element acts optionally and can pass responsibility. Stream pipelines implement this principle at a functional level, with each intermediate operation corresponding to a &ldquo;processing unit&rdquo;. The big difference is that there is no need to chain objects together manually; instead, the processing chain results from the fluent syntax of the API itself.</p><p>Overall, streams in Java lead to a functional re-contextualization of classic design patterns. This does not make many patterns superfluous but instead transforms them in their implementation. They now appear less rigid class structures and more as dynamic, composable units that seamlessly fit into fluent APIs. For the informed developer, this not only opens up new possibilities for expression but also a reflective understanding of when a pattern in the classic sense is still necessary. But I will report on this in a separate post.</p><h2 id="an-example-from-classic-java-to-the-use-of-streams">An example from classic Java to the use of streams</h2><p>A classic data processing algorithm in Java extracts, transforms, and aggregates information from a list of complex objects. Let&rsquo;s take a list of as an example Person-Objects, each containing name, age and place of residence. The goal is to extract all the names of adults, sort them alphabetically and return them as a string separated by commas. The classic imperative-object-oriented implementation of this requirement in Java typically takes place via explicit loops, temporary lists and manual control structures.</p><p>You usually start by creating a new results list in such an implementation. The original data is then iterated over, in one if-Condition it is checked whether the age of the person in question is over 17. If so, the person&rsquo;s name is added to the results list. Once the iteration is complete, the list is sorted alphabetically by explicitly calling the sort method. Finally, the list is created using a loop or by using a StringBuilder converted into a comma-separated string. This approach works correctly but requires a lot of intermediate steps and quickly leads to redundant or error-prone logic - for example, when sorting or formatting the result.</p><p>With the introduction of the Stream API, the same algorithm can be expressed in a much more concise and declarative manner. The entire data flow – from filtering to transformation to aggregation – can be modeled in a single expression unit. The Stream is derived from the list, then filters filter-Operation to remove adults. A subsequent one map-Transformation extracts the names. The sorting is done by sorted realized, and the final aggregation takes place via Collectors.joining(","). The entire algorithm can, therefore, be expressed in a fluent, clearly structured pipeline, the sub-steps of which are semantically self-explanatory through their method names.</p><p>The differences between the two approaches are both syntactic and conceptual. While the classic solution relies heavily on imperative thinking and controls each processing element manually, the stream approach allows declarative modeling of the &ldquo;what&rdquo; and leaves the &ldquo;how&rdquo; to be executed by the runtime environment. This level of abstraction promotes readability and maintainability of the code, as the developer can concentrate on the technical logic without being distracted by control flow constructs.</p><p>At the same time, it can be seen that streams also enable semantic compression. An algorithm that previously required several dozen lines is often reduced to a few clearly structured function calls. However, this compactness does not come at the expense of transparency - on the contrary, the descriptive method names and the fluent structure make the data flow explicitly understandable. The stream variant, therefore, not only appears more modern but also conceptually closer to the problem.</p><p>It can be said that the stream-based implementation integrates functional principles into the Java world without sacrificing type safety or object orientation. It makes it possible to formulate classic algorithms with a new level of abstraction that not only simplifies the code, but also expresses its intent more clearly.</p><p>But let&rsquo;s look at it in the source code: Let&rsquo;s assume that we have the following structures available for both implementations.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">record Person(String name, int age, String city) {}</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">private static final List<span class="nt">&lt;Person&gt;</span> PEOPLE = List.of(</span></span><span class="line"><span class="cl">   new Person("Alice", 23, "Berlin"),</span></span><span class="line"><span class="cl">   new Person("Bob", 16, "Hamburg"),</span></span><span class="line"><span class="cl">   new Person("Clara", 19, "München"),</span></span><span class="line"><span class="cl">   new Person("David", 17, "Köln")</span></span><span class="line"><span class="cl">);</span></span></code></pre></div></div><h3 id="example-in-classic-java-without-streams">Example in classic Java without streams</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">List<span class="nt">&lt;String&gt;</span> adultNames = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> for (Person p : PEOPLE) {</span></span><span class="line"><span class="cl"> if (p.age &gt;= 18) {</span></span><span class="line"><span class="cl"> adultNames.add(p.name);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> Collections.sort(adultNames);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> StringBuilder result = new StringBuilder();</span></span><span class="line"><span class="cl"> for (int i = 0; i<span class="nt">&lt; adultNames.size</span><span class="err">();</span><span class="err">i++)</span><span class="err">{</span></span></span><span class="line"><span class="cl"><span class="err">result.append(adultNames.get(i));</span></span></span><span class="line"><span class="cl"><span class="err">if</span><span class="err">(i</span><span class="err">&lt;</span><span class="err">adultNames.size()</span><span class="err">-</span><span class="err">1)</span><span class="err">{</span></span></span><span class="line"><span class="cl"><span class="err">result.append(",</span><span class="err">");</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span><span class="line"><span class="cl"><span class="err">System.out.println("Ergebnis:</span><span class="err">"</span><span class="err">+</span><span class="err">result);</span></span></span><span class="line"><span class="cl"><span class="err">}</span></span></span></code></pre></div></div><p>In the classic variant, the data flow is fragmented and distributed over several steps: filtering, collecting, sorting and formatting take place in separate sections, sometimes using temporary structures. This leads to increased complexity and potential sources of errors - for example when formatting the output string correctly.</p><h3 id="example-in-java-using-streams-api">Example in Java using Streams API</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">PEOPLE</span><span class="p">.</span><span class="na">stream</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">filter</span><span class="p">(</span><span class="n">p</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">p</span><span class="p">.</span><span class="na">age</span><span class="p">()</span><span class="w"/><span class="o">&gt;=</span><span class="w"/><span class="n">18</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">map</span><span class="p">(</span><span class="n">Person</span><span class="p">::</span><span class="n">name</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">sorted</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">collect</span><span class="p">(</span><span class="n">Collectors</span><span class="p">.</span><span class="na">joining</span><span class="p">(</span><span class="s">", "</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Ergebnis: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">result</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The stream variant, on the other hand, expresses the entire data processing process in a single pipeline. Each processing step is clearly named, the transformation takes place fluidly, and the result is immediately visible. Particularly noteworthy is the better one<em>readability</em> , the<em>Reduction of side effects</em> and the<em>Extensibility</em> – additional processing steps can be added by simply inserting additional operations into the pipeline without fundamentally changing the structure.</p><h2 id="changes-to-the-streams-api-from-java-8-to-21---an-overview">Changes to the Streams API from Java 8 to 21 - an overview</h2><p>Since its introduction in Java 8, the Stream API has fundamentally changed the way data is processed in Java. With the aim of enabling declarative, functionally inspired data flows, she established a new programming paradigm within the object-oriented language. The initial version was already remarkably expressive: it offered a clear separation between data source, transformation and terminal operation, supported lazy evaluation and could be executed both sequentially and in parallel. In particular the integration with lambdas, method references and the java.util.functionlibrary enabled a fluid, type-safe style of data processing that was previously only possible via external libraries or explicit iterators.</p><p>With the subsequent versions of the JDK, the Stream API was not fundamentally redesigned, but was continually expanded, refined and stabilized. Java 9 brought with it the first significant enhancements, such as the methods causeWhile, dropWhile and iterate with predicates that made it possible to control stream pipelines even more precisely and terminate them early. These extensions closed a semantic gap in the original API and, in particular, improved the ability to model more complex data flows. Also the introduction of Optional. Stream () was a notable move as it provided an elegant bridge between the types Optional and Stream and thus further increased the ability to compose in a functional style.</p><p>In Java 10 to 14, priority was given to accompanying language features such as our introduced, which did not directly change the Stream API itself, but improved its readability and applicability in many contexts. It was only with Java 16 and the consistent spread of records as compact data containers that stream processing became more expressive again, as it could now be combined with structured but immutable data models - a clear advantage for parallel or deterministic processing scenarios.</p><p>Java 17, as a long-term support version, consolidated the API through additional optimizations in the backend and further improved integration with pattern matching and modern sealed-Class hierarchy. These structural improvements did not introduce new methods in the Stream class itself, but enabled more precise handling of heterogeneous data flows and the formulation of domain-specific pipelines at a high level of abstraction.</p><p>It was only Java 21 that brought tangible expansions in the area of ​​functional data processing, especially in interaction Scoped Values, structured concurrency and the further developed ForkJoinPool implementation. While the Stream API received hardly any new methods formally, its applicability in the context of concurrent and memory-optimized architectures was significantly strengthened. The increased performance with parallel execution and the more efficient management of intermediate results also reflect a maturation of the API at the implementation level. With Java 21, streams can not only be written more elegantly but also executed more securely and predictably - especially in asynchronous or reactive processing environments.</p><p>In summary, the Stream API has undergone a clear maturation process from Java 8 to Java 21. While its conceptual core remained essentially constant, its semantic scope was clarified, its integration with modern linguistic means was deepened, and its efficiency was optimized on several levels.</p><h2 id="a-concrete-example-of-flatmap-and-mapmulti">A concrete example of flatMap and mapMulti</h2><h3 id="conceptual-difference">Conceptual difference</h3><p>flatMap was already introduced with Java 8 and is based on the idea of ​​applying a function that creates a new one for each element of the original stream<em>Stream</em> produced. These nested streams are then &ldquo;flat&rdquo; merged so that the resulting Stream has a flat structure. This pattern is compelling and allows elegant transformations of nested data structures - for example Stream&lt;List<T>&gt; to Stream<T>.</p><p>mapMulti(), introduced in Java 16, takes a different approach. Instead of creating nested streams, one is created here<em>Consumer based</em> Interface used: The method receives one for each element of the Stream BiConsumer, through which the developer can directly deliver zero, one or more output values ​​to the downstream - without creating temporary data structures such as lists or streams. This avoids heap allocations, which plays a particularly relevant role in performance-critical scenarios.</p><p>In the following example, let&rsquo;s assume that we have the following data structure.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">private static List<span class="nt">&lt;List</span><span class="err">&lt;String</span><span class="nt">&gt;</span>&gt; DATA = List.of(</span></span><span class="line"><span class="cl"> List.of("a", "b"),</span></span><span class="line"><span class="cl"> List.of("c"),</span></span><span class="line"><span class="cl"> List.of(),</span></span><span class="line"><span class="cl"> List.of("d", "e")</span></span><span class="line"><span class="cl">);</span></span></code></pre></div></div><h3 id="example-with-flatmap">Example with flatMap</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">List<span class="nt">&lt;String&gt;</span> result = DATA</span></span><span class="line"><span class="cl"> .stream()</span></span><span class="line"><span class="cl"> .flatMap(List::stream)</span></span><span class="line"><span class="cl"> .collect(Collectors.toList());</span></span></code></pre></div></div><p>Here each inner list is converted into a stream and then &ldquo;flattened&rdquo;.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">List<span class="nt">&lt;String&gt;</span> result = DATA</span></span><span class="line"><span class="cl"> .stream()</span></span><span class="line"><span class="cl"> .<span class="nt">&lt;String&gt;</span>mapMulti(Iterable::forEach)</span></span><span class="line"><span class="cl"> .toList();</span></span></code></pre></div></div><p>In this case, no new stream instance is created. Instead, it is about the forEach-Loop the content passed directly to the consumer.</p><p>flatMap stands more for declarative clarity and conceptual simplicity. The method represents a fundamental principle of functional programming and is therefore particularly suitable if the transformation is based on existing Stream<T>-generating functions takes place. It&rsquo;s great for teaching, readability, and functional reasoning, but does introduce some overhead from temporary streams.</p><p>mapMulti(), however, aims to<em>optimize</em> away. It allows transformations to be expressed more efficiently by granting direct control over the output. This reduces heap allocations, allows better memory locality and is therefore preferable from a JVM optimization perspective if performance is a priority. However, the semantic expression is more complex: the user must explicitly say so Consumer-Logic work, which can limit readability and comprehensibility for functionally less experienced developers.</p><p>You can say that flatMap, the idiomatic, functional way, remains, while mapMulti() represents a targeted tool for high-performance data processing. Their coexistence within the Stream API demonstrates Java&rsquo;s ambition to combine both expressiveness and efficiency within the same paradigm - a balancing act between declarative elegance and system-level control.</p><h2 id="whats-new-with-java-24---gatherer">What&rsquo;s new with Java 24 - Gatherer</h2><p>With Java 24, the Stream API has been expanded to include a new concept called<em>Gatherer</em> company. This addition addresses a long-standing gap in stream processing: the ability to perform custom aggregations across multiple elements in a controlled, stateful manner - but not at the end of the pipeline, as is the case with Collector-Instances is the case, but<em>while</em> of the stream process itself, as an integral part of the transformation. Gatherers represent a new class of intermediate operations that were specifically designed for advanced data flow logic.</p><p>The fundamental motivation for introducing gatherers is the limitation of the previous API in dealing with compound, multi-step, or stateful operations, particularly in the case of transformations that do not just produce a single element per input, but require sequencing, delaying, or grouping across multiple input elements. Previous tools like map, flatMap or peek all operate either statelessly or with limited context. For more complex cases, specialized iterator implementations or external libraries had to be used - which contradicts the goal of declarative data flow modelling in streams.</p><p>Gatherers solve this problem through a new processing model based on a controlled state. Similar to CollectorInstances define a set of functions that allow elements to be buffered, transformed and emitted - not at the end of the Stream, but as part of the ongoing data flow. So they combine the conceptual strengths of Collector and mapMulti, but extend these to include explicit support for temporary states and flexible emission strategies. There will be one Sink used to deliver any results downstream - even several per input element or even delayed.</p><p>The purpose of this expansion lies not only in expressivity but also in performance. Many use cases, such as sliding windows, temporal aggregations, sequence analysis or transformation logic with history, can be modelled precisely and efficiently with gatherers without sacrificing the declarative structure of the pipeline. This provides the developer with a tool that explicitly allows intermediate states and context dependency within stream processing but is type-safe, controlled and idiomatically embedded in the existing API.</p><p>This opens up new perspectives in development. Gatherers enable complex data flow modelling with a declarative character without falling back into imperative control logic. They significantly expand the semantic space of streams and thus represent a logical next step in the evolution of stream architecture - comparable to the introduction of Collector or mapMulti. Its introduction shows that Java integrates functional principles and develops them further in state models, process control and efficiency - without the compromises that often accompany system-level optimization.</p><h2 id="practical-examples-with-the-gatherer">Practical examples with the Gatherer</h2><p>The introduction of gatherers not only created a new extension of stream processing but also provided a collection of predefined gatherers that address typical use cases that were previously difficult to model. These predefined gatherers combine declarative expressiveness with stateful transformation, enabling new forms of data stream processing - especially for sequential, grouped or sequentially correlated data flows. The central representatives of this new genre include windowFixed, windowSliding, fold and scan. Each of these gatherers brings specific behaviour that previously had to be implemented with considerable manual effort or even outside of the stream API.</p><h3 id="windowfixed">windowFixed</h3><p>The Gatherer windowFixed allows a stream to be divided into equally sized, non-overlapping windows. This is particularly relevant if data is to be further processed or aggregated in blocks. Suppose we want to split a list of integers into groups of five and calculate the sum of each group. With windowFixed This is very elegant:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">List<span class="nt">&lt;Integer&gt;</span> input = IntStream.rangeClosed(1, 15).boxed().toList();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">List<span class="nt">&lt;Integer&gt;</span> resultGatherer = input.stream()</span></span><span class="line"><span class="cl"> .gather(Gatherers.windowFixed(5))</span></span><span class="line"><span class="cl"> .map(window -&gt; window.stream().reduce(0, Integer::sum))</span></span><span class="line"><span class="cl"> .toList(); // [15, 40, 65]</span></span><span class="line"><span class="cl">System.out.println("resultGatherer = " + resultGatherer);</span></span></code></pre></div></div><p>The alternative without a gatherer could look like this.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">List<span class="nt">&lt;Integer&gt;</span> result = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">List<span class="nt">&lt;Integer&gt;</span> window = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl">for (int i : input) {</span></span><span class="line"><span class="cl"> window.add(i);</span></span><span class="line"><span class="cl"> if (window.size() == 5) {</span></span><span class="line"><span class="cl"> result.add(window.stream().reduce(0, Integer::sum));</span></span><span class="line"><span class="cl"> window.clear();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl">System.out.println("result = " + result);</span></span></code></pre></div></div><p>The difference is clear: The Gatherer variant is not only more compact, but also clearly separates data flow and logic, while the imperative solution works with side effects and state management.</p><h3 id="windowsliding">windowSliding</h3><p>A related but semantically more sophisticated case is given by windowSliding covered. These are sliding windows - each new element moves the window by one.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">List<span class="nt">&lt;Integer&gt;</span> input = List.of(1, 2, 3, 4, 5);</span></span><span class="line"><span class="cl">List<span class="nt">&lt;Double&gt;</span> result = input.stream()</span></span><span class="line"><span class="cl"> .gather(Gatherers.windowSliding(3))</span></span><span class="line"><span class="cl"> .map(window -&gt; window.stream().mapToInt(Integer::intValue).average().orElse(0))</span></span><span class="line"><span class="cl"> .toList(); // [2.0, 3.0, 4.0]</span></span></code></pre></div></div><p>The given Java source code demonstrates an application des Gatherers windowSliding(n). This is used in this example to create sliding windows - so-called - over a given list of integers<strong>Sliding Windows</strong> – to generate. The database in this case is an immutable list of integers:</p><p><strong>List<Integer> input = List.of(1, 2, 3, 4, 5);</strong></p><p>The subsequent stream processing aims to calculate the arithmetic mean (average) of the elements contained over each sliding window of size three. The method windowSliding(3) causes the data stream to be internally split into overlapping windows, with each window containing three consecutive elements. For the list [1, 2, 3, 4, 5] This results in the following windows:</p><ul><li>[1, 2, 3]</li><li>[2, 3, 4]</li><li>[3, 4, 5]</li></ul><p>Within the mapoperator will turn each of these windows into one Stream<Integer> converted, where with mapToInt(Integer::intValue).average() the average value is calculated. This method provides one OptionalDouble, from which means orElse(0) a double is extracted if the window is empty (which is impossible in this use case because the window size is smaller or equal to the initial list).</p><p>The resulting List<Double> contains the average values ​​of the overlapping triples and looks specifically as follows:</p><p>[2.0, 3.0, 4.0]</p><p>The code&rsquo;s declarative style promotes readability and understanding, while the Gatherers-API provides an elegant way to perform sliding aggregations directly in the stream context without resorting to manual window logic or imperative loops.</p><h3 id="possible-optimizations">possible optimizations</h3><p>Is Several optimization goals can be identified that can improve both the robustness against runtime errors and the performance and semantic readability of the code. Each of these goals brings different requirements for the design of the code.</p><p>Regarding performance<strong>optimization</strong> , it can be determined that the current source code creates unnecessary objects by repeatedly creating temporary ones, such as stream objects within the mapping. This can lead to a noticeable increase in the garbage collection load, especially with large amounts of data. To increase performance, you should ensure repeated use of Stream () within the window and instead directly with the existing one List<Integer> work provided through windowSliding. An alternative solution could look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">List<span class="nt">&lt;Double&gt;</span> result = input.stream()</span></span><span class="line"><span class="cl"> .gather(Gatherers.windowSliding(3))</span></span><span class="line"><span class="cl"> .map(window -&gt; {</span></span><span class="line"><span class="cl"> int sum = 0;</span></span><span class="line"><span class="cl"> for (int i : window) sum += i;</span></span><span class="line"><span class="cl"> return sum / 3.0;</span></span><span class="line"><span class="cl"> })</span></span><span class="line"><span class="cl"> .toList();</span></span></code></pre></div></div><p>This version completely eliminates the overhead of internal stream processing per window. Instead, the summation is done directly via a simple loop. This not only reduces the allocation of temporary objects, but allows the JVM to perform aggressive inlining and loop unrolling optimizations. In addition, the division by the window size is constant, since windowSliding(3) a fixed window size guaranteed. However, if the window size varies dynamically, you could alternatively window.size() determine at runtime without endangering semantic correctness.</p><p>Regarding the<strong>readability</strong> , It should be noted that functional elegance and imperatively expressed clarity are not necessarily in contradiction. The original version with mapToInt(&hellip;).average().orElse(0) Although it is syntactically compact, it is less immediately understandable for many developers, especially with regard to the treatment of OptionalDouble. The imperative variant with direct summation and division, on the other hand, clearly reveals the underlying intention - the calculation of an average over three elements - without a deep understanding of the Stream-API is required. For teams with heterogeneous levels of experience or in code bases with a strong focus on maintainability, the following version is often preferable:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">List<span class="nt">&lt;Double&gt;</span> result = input.stream()</span></span><span class="line"><span class="cl"> .gather(Gatherers.windowSliding(3))</span></span><span class="line"><span class="cl"> .map(window -&gt; {</span></span><span class="line"><span class="cl"> int sum = 0;</span></span><span class="line"><span class="cl"> for (Integer value : window) {</span></span><span class="line"><span class="cl"> sum += value;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> return (double) sum / window.size();</span></span><span class="line"><span class="cl"> })</span></span><span class="line"><span class="cl"> .toList();</span></span></code></pre></div></div><p>This variant avoids potentially confusing method chains and expresses the calculation step by step. This not only makes the code easier to understand, but also easier to test, as each calculation step can be validated individually. At the same time, it remains compact and uses modern Java constructs such as Stream.gather() in an idiomatic way.</p><p>Overall, it can be seen that by taking robustness, performance and readability into account, both the functional and structural quality of the code can be increased. The choice of specific optimization always depends on the application context and the requirements for runtime behavior, error handling and maintainability.</p><p>That should be it at this point for now. In the following post I will go into more detail about the practical use of streams, the use of gatherers and similar things.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Streams</category><media:content url="https://svenruppert.com/images/2025/03/ChatGPT-Image-29.-Marz-2025-13_06_16.png" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2025/03/ChatGPT-Image-29.-Marz-2025-13_06_16.png"/><enclosure url="https://svenruppert.com/images/2025/03/ChatGPT-Image-29.-Marz-2025-13_06_16.png" type="image/jpeg" length="0"/></item><item><title>TornadoVM - Boosting the Concurrency</title><link>https://svenruppert.com/posts/tornadovm-boosting-the-concurrency/</link><pubDate>Sat, 23 Nov 2024 19:40:08 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/tornadovm-boosting-the-concurrency/</guid><description>TornadoVM is an open-source framework that extends the Java Virtual Machine (JVM) to support hardware accelerators such as Graphics Processing Units (GPUs), Field-Programmable Gate Arrays (FPGAs), and multi-core central processing units (CPUs). This allows developers to accelerate their Java programs on heterogeneous hardware without needing to rewrite their code in low-level languages such as CUDA or OpenCL.</description><content:encoded>&lt;![CDATA[<p>TornadoVM is an open-source framework that extends the Java Virtual Machine (JVM) to support hardware accelerators such as Graphics Processing Units (GPUs), Field-Programmable Gate Arrays (FPGAs), and multi-core central processing units (CPUs). This allows developers to accelerate their Java programs on heterogeneous hardware without needing to rewrite their code in low-level languages such as CUDA or OpenCL.</p><ol><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#motivation-and-background">Motivation and Background</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#how-tornadovm-works">How TornadoVM Works</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#key-features">Key Features</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#use-cases">Use Cases</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#performance-and-benchmarks">Performance and Benchmarks</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#limitations-and-challenges">Limitations and Challenges</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#integration-with-java-ecosystem">Integration with Java Ecosystem</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#future-directions">Future Directions</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#theoretical-background-parallelism-in-matrix-multiplication">Theoretical Background: Parallelism in Matrix Multiplication</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#how-tornadovm-exploits-parallelism">How TornadoVM Exploits Parallelism</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#example-matrix-multiplication-in-java-with-tornadovm">Example: Matrix Multiplication in Java with TornadoVM</a>
1.<a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#code-explanation">Code Explanation</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#theoretical-discussion">Theoretical Discussion</a></li><li><a href="https://svenruppert.com/2024/09/12/tornadovm-boosting-the-concurrency/#performance-considerations">Performance Considerations</a></li></ol><p>Here&rsquo;s an overview of TornadoVM&rsquo;s key aspects and features:</p><h3 id="motivation-and-background">Motivation and Background</h3><p>The growth in heterogeneous computing, driven by the increasing demand for performance in fields like machine learning, data processing, and scientific computing, has led to the development of specialised hardware such as GPUs and FPGAs. These devices offer significant performance improvements over traditional CPUs for parallel workloads.</p><p>However, programming for these accelerators traditionally requires low-level programming models like CUDA or OpenCL, which are less developer-friendly than higher-level languages like Java. TornadoVM addresses this challenge by allowing developers to use the Java programming language while transparently offloading computations to hardware accelerators.</p><p>The JVM has been traditionally optimised for multi-core CPUs, but its design is unsuited for exploiting massively parallel architectures like GPUs. TornadoVM fills this gap by introducing a runtime and compilation infrastructure that allows the JVM to leverage these devices.</p><h3 id="how-tornadovm-works">How TornadoVM Works</h3><p>TornadoVM provides an intermediate layer between Java applications and the underlying hardware. It intercepts certain portions of the Java code, such as loops and parallelizable methods, and compiles them to run on GPUs, FPGAs, or multi-core CPUs.</p><p>The TornadoVM runtime is divided into three main components:</p><p><strong>Bytecode Analyzer</strong> : TornadoVM analyses the Java bytecode to identify portions of the code that can be accelerated. This typically includes loops, data-parallel operations, and compute-intensive methods.</p><p><strong>JIT Compiler</strong> : TornadoVM uses a Just-In-Time (JIT) compiler to translate the selected portions of the bytecode into OpenCL or PTX (NVIDIA&rsquo;s parallel thread execution format) code that can run on GPUs or FPGAs. The compiler also optimises the code for the specific hardware target.</p><p><strong>Task Scheduling and Execution</strong> : Once compiled, TornadoVM schedules the execution tasks on the available hardware. It manages data transfers between the host (CPU) and the accelerators and ensures the results are correctly integrated into the running Java program.</p><h3 id="key-features">Key Features</h3><p><strong>Java API</strong> : TornadoVM provides a simple Java API that allows developers to annotate the code they want to accelerate. This can include compute-intensive methods or loops suitable for parallel execution.</p><p><strong>Automatic Offloading</strong> : TornadoVM automatically detects which portions of the code can be offloaded to the hardware accelerators. The developer doesn&rsquo;t need to manage low-level details such as memory transfers or kernel execution.</p><p><strong>Multi-backend Support</strong> : TornadoVM supports multiple backends, including OpenCL, PTX (for NVIDIA GPUs), and SPIR-V. This enables it to target various hardware platforms, from different GPU vendors (NVIDIA, AMD, Intel) to FPGAs.</p><p><strong>Data Management</strong> : TornadoVM handles data transfers between the main memory (used by the CPU) and the memory of the accelerators (such as the GPU memory). It uses a memory management system to minimise unnecessary data transfers, reduce overhead, and improve performance.</p><p><strong>Heterogeneous Task Scheduling</strong> : TornadoVM can execute multiple tasks across different devices in parallel. For example, it can execute one part of the code on a CPU and another part on a GPU, enabling efficient use of all available hardware resources.</p><h3 id="use-cases">Use Cases</h3><p>TornadoVM is particularly suited for applications that can benefit from parallel execution. Some of the most common use cases include:</p><p><strong>Machine Learning</strong> : Many machine learning algorithms, especially those based on matrix operations (like neural networks), can be accelerated on GPUs or FPGAs using TornadoVM.</p><p><strong>Big Data Processing</strong> : Frameworks like Apache Spark can be integrated with TornadoVM to accelerate data processing pipelines. By offloading certain operations to GPUs, it can significantly reduce the time required to process large datasets.</p><p><strong>Scientific Computing</strong> : Many scientific applications involve complex mathematical computations that can benefit from parallel execution on hardware accelerators. TornadoVM makes it possible to run these applications on GPUs without rewriting the code in CUDA or OpenCL.</p><p><strong>Financial Modeling</strong> : Algorithms used in financial simulations, such as Monte Carlo simulations or options pricing, often involve parallelizable computations that can be offloaded to hardware accelerators using TornadoVM.</p><h3 id="performance-and-benchmarks">Performance and Benchmarks</h3><p>TornadoVM has been shown to improve performance significantly for various applications. For example, benchmarks have demonstrated that TornadoVM can speed up certain machine learning algorithms by 10x to 100x when running on GPUs, compared to the same code running on a CPU.</p><p>The performance gains are particularly significant for compute-bound applications that involve a large number of parallel operations. However, not all applications will benefit from TornadoVM. Applications that are I/O-bound or involve a lot of sequential processing may not see significant speedups.</p><h3 id="limitations-and-challenges">Limitations and Challenges</h3><p>Despite its benefits, TornadoVM has some limitations:</p><p><strong>Device Availability</strong> : TornadoVM requires the presence of a hardware accelerator (such as a GPU or FPGA) to provide performance improvements. If no such device is available, the code will fall back to running on the CPU.</p><p><strong>Limited Scope</strong> : TornadoVM is most effective for applications that involve parallelizable computations. Applications with complex control flow or those that are heavily reliant on I/O may not see significant performance improvements.</p><p><strong>Hardware-Specific Tuning</strong> : While TornadoVM abstracts many of the details of hardware acceleration, developers may still need to fine-tune their code for specific hardware platforms to achieve optimal performance.</p><h3 id="integration-with-java-ecosystem">Integration with Java Ecosystem</h3><p>One of the key strengths of TornadoVM is its seamless integration with the existing Java ecosystem. It works with the standard Java Development Kit (JDK) and can be integrated with popular Java frameworks such as:</p><p><strong>Apache Spark</strong> : TornadoVM can be used to accelerate Spark workloads by offloading certain operations to hardware accelerators. This can significantly reduce the time required to process large datasets.</p><p><strong>JVM-based Languages</strong> : TornadoVM can be used with any JVM-based language, including Scala, Kotlin, and Groovy. This makes it a versatile solution for developers working in various JVM languages.</p><p><strong>GraalVM</strong> : TornadoVM can also be used in conjunction with GraalVM, an advanced JVM that includes an optimising compiler and support for polyglot programming. GraalVM can further enhance TornadoVM&rsquo;s performance by applying additional optimizations at runtime.</p><h3 id="future-directions">Future Directions</h3><p>TornadoVM is an actively developed project, and its developers are working on several features to improve its functionality and performance. Some of the key areas of future development include:</p><p><strong>Expanded Hardware Support</strong> : TornadoVM continually supports new hardware platforms, including newer generations of GPUs and FPGAs.</p><p><strong>Improved Optimization</strong> : The TornadoVM team is working on improving the JIT compiler to generate more optimised code for hardware accelerators, leading to even more significant performance gains.</p><p><strong>Automatic Parallelization</strong> : While TornadoVM already supports automatic offloading of specific code segments, there is ongoing work to automate further the process of identifying parallelizable code and offloading it to hardware accelerators.</p><p><strong>Deep Integration with Machine Learning Frameworks</strong> : TornadoVM is exploring deeper integration with machine learning frameworks like TensorFlow and PyTorch to provide hardware acceleration for a wider range of machine learning tasks.</p><p>TornadoVM is a powerful tool that brings hardware acceleration to the Java ecosystem, allowing developers to harness the power of GPUs, FPGAs, and multi-core CPUs without having to leave the familiar world of the JVM. By simplifying the process of offloading computations to heterogeneous hardware, TornadoVM opens up new possibilities for accelerating a wide range of applications, from machine learning and big data processing to scientific computing and financial modelling. As the demand for high-performance computing continues to grow, TornadoVM is likely to play an increasingly important role in enabling Java developers to take full advantage of modern hardware platforms.</p><h2 id="lets-have-an-example">Let&rsquo;s have an example</h2><p>To give you a comprehensive example in Java using TornadoVM, we will look at simple matrix multiplication, a common parallelisable operation. The example will include a theoretical background on parallelism, how TornadoVM exploits it, and how the program is structured to benefit from GPU/FPGA acceleration.</p><h3 id="theoretical-background-parallelism-in-matrix-multiplication">Theoretical Background: Parallelism in Matrix Multiplication</h3><p>Matrix multiplication is a good example of parallel execution because each element of the result matrix can be computed independently of the others. Consider two matrices,<code>A</code> and<code>B</code>, where:</p><p>- Matrix<code>A</code> is of size N x M,</p><p>- Matrix<code>B</code> is of size M x K.</p><p>The resulting matrix<code>C</code> will be of size N x K, where each element C[i][j] is the dot product of the i-th row of<code>A</code> and the j-th column of<code>B</code>:</p><figure><img src="/images/2024/09/Bildschirmfoto-2024-09-11-um-21.23.34.png" alt="" loading="lazy" decoding="async"/><p>This operation involves a large number of independent computations, meaning that all elements of<code>C</code> can be computed in parallel. This is an ideal scenario for hardware acceleration using GPUs or FPGAs, which can perform a massive number of operations simultaneously.</p><h3 id="how-tornadovm-exploits-parallelism">How TornadoVM Exploits Parallelism</h3><p>TornadoVM can accelerate the matrix multiplication by identifying the parallelisable loops (i.e., loops where the computations are independent). It translates the loops into tasks that can be distributed across GPU cores. The key steps are:</p><p><strong>Bytecode Analysis</strong> : TornadoVM inspects the Java bytecode to identify parallelisable sections, such as loops where each iteration performs independent operations.</p><p><strong>JIT Compilation</strong> : The identified sections are compiled Just-In-Time (JIT) into code that runs on the accelerator (such as PTX for NVIDIA GPUs).</p><p><strong>Task Execution</strong> : The compiled tasks are executed on the available hardware, while data is transferred between the host (CPU) and the accelerator as needed.</p><h3 id="example-matrix-multiplication-in-java-with-tornadovm">Example: Matrix Multiplication in Java with TornadoVM</h3><p>Below is a Java code example that uses TornadoVM to perform matrix multiplication.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">uk.ac.manchester.tornado.api.TaskSchedule</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">uk.ac.manchester.tornado.api.TornadoVM</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">uk.ac.manchester.tornado.api.annotations.Parallel</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MatrixMultiplication</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Matrix multiplication using TornadoVM</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">multiplyMatrix</span><span class="p">(</span><span class="kt">float</span><span class="o">[][]</span><span class="w"/><span class="n">A</span><span class="p">,</span><span class="w"/><span class="kt">float</span><span class="o">[][]</span><span class="w"/><span class="n">B</span><span class="p">,</span><span class="w"/><span class="kt">float</span><span class="o">[][]</span><span class="w"/><span class="n">C</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">N</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">M</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">K</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// TornadoVM annotation to indicate parallelism</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nd">@Parallel</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">N</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">j</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">j</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">K</span><span class="p">;</span><span class="w"/><span class="n">j</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kt">float</span><span class="w"/><span class="n">sum</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">.</span><span class="na">0f</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">k</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">k</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">M</span><span class="p">;</span><span class="w"/><span class="n">k</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="n">sum</span><span class="w"/><span class="o">+=</span><span class="w"/><span class="n">A</span><span class="o">[</span><span class="n">i</span><span class="o">][</span><span class="n">k</span><span class="o">]</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">B</span><span class="o">[</span><span class="n">k</span><span class="o">][</span><span class="n">j</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">C</span><span class="o">[</span><span class="n">i</span><span class="o">][</span><span class="n">j</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">sum</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">int</span><span class="w"/><span class="n">N</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1024</span><span class="p">;</span><span class="w"/><span class="c1">// Number of rows in A</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">int</span><span class="w"/><span class="n">M</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1024</span><span class="p">;</span><span class="w"/><span class="c1">// Number of columns in A (and rows in B)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">int</span><span class="w"/><span class="n">K</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1024</span><span class="p">;</span><span class="w"/><span class="c1">// Number of columns in B</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Initialize matrices</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">float</span><span class="o">[][]</span><span class="w"/><span class="n">A</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">float</span><span class="o">[</span><span class="n">N</span><span class="o">][</span><span class="n">M</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">float</span><span class="o">[][]</span><span class="w"/><span class="n">B</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">float</span><span class="o">[</span><span class="n">M</span><span class="o">][</span><span class="n">K</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">float</span><span class="o">[][]</span><span class="w"/><span class="n">C</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">float</span><span class="o">[</span><span class="n">N</span><span class="o">][</span><span class="n">K</span><span class="o">]</span><span class="p">;</span><span class="w"/><span class="c1">// Result matrix</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Fill matrices A and B with random values</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">N</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">j</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">j</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">M</span><span class="p">;</span><span class="w"/><span class="n">j</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">A</span><span class="o">[</span><span class="n">i</span><span class="o">][</span><span class="n">j</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">random</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">M</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">j</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">j</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">K</span><span class="p">;</span><span class="w"/><span class="n">j</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">B</span><span class="o">[</span><span class="n">i</span><span class="o">][</span><span class="n">j</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">float</span><span class="p">)</span><span class="w"/><span class="n">Math</span><span class="p">.</span><span class="na">random</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Create a task schedule for TornadoVM</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">TaskSchedule</span><span class="w"/><span class="n">ts</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TaskSchedule</span><span class="p">(</span><span class="s">"s0"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="na">task</span><span class="p">(</span><span class="s">"t0"</span><span class="p">,</span><span class="w"/><span class="n">MatrixMultiplication</span><span class="p">::</span><span class="n">multiplyMatrix</span><span class="p">,</span><span class="w"/><span class="n">A</span><span class="p">,</span><span class="w"/><span class="n">B</span><span class="p">,</span><span class="w"/><span class="n">C</span><span class="p">,</span><span class="w"/><span class="n">N</span><span class="p">,</span><span class="w"/><span class="n">M</span><span class="p">,</span><span class="w"/><span class="n">K</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">.</span><span class="na">mapAllTo</span><span class="p">(</span><span class="n">TornadoVM</span><span class="p">.</span><span class="na">getDefaultDevice</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Execute the task on the GPU</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">ts</span><span class="p">.</span><span class="na">execute</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Print out a few results for verification</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Result (C[0][0]): "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">C</span><span class="o">[</span><span class="n">0</span><span class="o">][</span><span class="n">0</span><span class="o">]</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Result (C[10][10]): "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">C</span><span class="o">[</span><span class="n">10</span><span class="o">][</span><span class="n">10</span><span class="o">]</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h4 id="code-explanation">Code Explanation</h4><p><strong>Matrix Initialization</strong> :</p><p>Matrices<code>A</code> and<code>B</code> are initialised with random floating-point values. The matrix<code>C</code> will store the result of the multiplication.</p><p><strong>Matrix Multiplication Function</strong> :</p><p>The<code>multiplyMatrix</code> method multiplies matrix<code>A</code> by matrix<code>B</code> and stores the result in matrix<code>C</code>. The<strong>@Parallel</strong> annotation tells TornadoVM that this loop is parallelisable, meaning that each computation of<code>C[i][j]</code> can be executed independently across different threads or hardware cores.</p><p><strong>Task Scheduling with TornadoVM</strong> :</p><p>The<code>TaskSchedule</code> object schedules a TornadoVM task, where the<code>multiplyMatrix</code> function is executed.<code>.mapAllTo(TornadoVM.getDefaultDevice())</code> specifies that the entire task will be mapped to the default TornadoVM device, which could be a GPU, FPGA, or multi-core CPU, depending on the available hardware. The<code>.execute()</code> method runs the task, performing the matrix multiplication on the selected hardware accelerator.</p><p><strong>Performance Consideration</strong> :</p><p>For large matrix sizes (e.g., 1024x1024, as used in the example), the GPU can perform the multiplication much faster than a CPU by exploiting parallelism. TornadoVM handles the data transfer and task offloading, ensuring efficient execution on the accelerator.</p><h3 id="theoretical-discussion">Theoretical Discussion</h3><p><strong>Parallelism</strong> :</p><p>Each element C[i][j] is independent of all other elements in matrix multiplication, making the operation embarrassingly parallel. The GPU can execute these independent operations concurrently with its thousands of cores, leading to significant speedups over a sequential CPU-based implementation.</p><p><strong>Data Transfer</strong> :</p><p>TornadoVM manages data transfers between the CPU (host) and the GPU (device). Before the computation starts, matrices A and B are copied to the GPU&rsquo;s memory. After the computation, the resulting matrix<code>C</code> is transferred back to the CPU. TornadoVM optimised these transfers, ensuring that only the necessary data is copied and avoiding redundant transfers to minimise overhead.</p><p><strong>Memory Coalescing</strong> :</p><p>Memory access patterns are important for performance on a GPU. TornadoVM attempts to optimise memory access through techniques like memory coalescing, where adjacent threads access adjacent memory locations, reducing the number of memory transactions and improving bandwidth utilisation.</p><p><strong>Loop Parallelization</strong> :</p><p>The loop structure is a perfect candidate for parallelisation: TornadoVM splits the outer two loops (over<code>i' and</code>j<code>indices) across different GPU threads. Each thread computes a single element of the result matrix</code>C<code>, while the innermost loop (over</code>k`) can be executed within each thread.</p><p><strong>Just-in-Time Compilation</strong> :</p><p>TornadoVM&rsquo;s JIT compiler generates GPU-specific code (such as PTX code for NVIDIA GPUs) based on the annotated Java bytecode. This process occurs at runtime, allowing TornadoVM to optimise the code for the specific hardware platform in use.</p><h3 id="performance-considerations">Performance Considerations</h3><p>Using TornadoVM on a GPU or FPGA for matrix multiplication can yield significant performance improvements, especially for large matrices. For instance:</p><p>A CPU may take several seconds to multiply two large matrices (e.g., 1024x1024), whereas a GPU can perform the same operation in a fraction of the time by leveraging its thousands of cores. The larger the matrices, the more efficient TornadoVM becomes, as the overhead of transferring data between the CPU and the accelerator becomes less significant relative to the computation time.</p><p>This example illustrates how TornadoVM allows Java developers to exploit hardware accelerators like GPUs for computationally expensive tasks like matrix multiplication. By identifying parallelisable loops and offloading them to the accelerator, TornadoVM achieves substantial performance improvements while allowing developers to continue working in Java without needing lower-level languages like CUDA or OpenCL.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Uncategorized</category><media:content url="https://svenruppert.com/images/2024/09/EA3533F5-A7E8-413C-896F-EB16D657AC11.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/09/EA3533F5-A7E8-413C-896F-EB16D657AC11.jpg"/><enclosure url="https://svenruppert.com/images/2024/09/EA3533F5-A7E8-413C-896F-EB16D657AC11.jpg" type="image/jpeg" length="0"/></item><item><title>Cache Poisoning Attacks on Dependency Management Systems like Maven</title><link>https://svenruppert.com/posts/cache-poisoning-attacks-on-dependency-management-systems-like-maven/</link><pubDate>Wed, 13 Nov 2024 14:15:16 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/cache-poisoning-attacks-on-dependency-management-systems-like-maven/</guid><description>Cache poisoning on Maven Caches is a specific attack that targets how Maven Caches manages packages and dependencies in a software development process. It&rsquo;s essential to understand how Maven works before we look at the details of cache poisoning.</description><content:encoded>&lt;![CDATA[<p>Cache poisoning on Maven Caches is a specific attack that targets how Maven Caches manages packages and dependencies in a software development process. It&rsquo;s essential to understand how Maven works before we look at the details of cache poisoning.</p><ol><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#overview-of-maven-and-its-caches">Overview of Maven and its caches</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#what-is-cache-poisoning">What is cache poisoning?</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#types-of-cache-poisoning-on-maven-caches">Types of cache poisoning on Maven caches</a><ol><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#man-in-the-middle-mitm-cache-poisoning">Man-in-the-Middle (MITM) Cache Poisoning</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#exploit-repository-vulnerabilities">Exploit repository vulnerabilities</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#dependency-confusion">Dependency Confusion</a><ol><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#basics-of-dependency-confusion">Basics of Dependency Confusion</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#how-dependency-confusion-was-discovered">How Dependency Confusion Was Discovered</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#why-does-dependency-confusion-work-so-effectively">Why does Dependency Confusion work so effectively?</a></li></ol></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#typosquatting">Typosquatting</a><ol><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#basics-of-typosquatting">Basics of typosquatting</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#typical-typosquatting-techniques">Typical typosquatting techniques</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#dangers-of-typosquatting">Dangers of typosquatting</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#maven-typosquatting-cases">Maven typosquatting cases</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#process-of-a-cache-poisoning-attack-on-maven">Process of a cache poisoning attack on Maven</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#security-mechanisms-to-protect-against-cache-poisoning">Security mechanisms to protect against cache poisoning</a><ol><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#regular-updates-and-patch-management">Regular updates and patch management</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#using-https">Using HTTPS</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#signature-verification">Signature verification</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#improve-repository-security">Improve repository security</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#dependency-scanning-and-monitoring">Dependency Scanning and Monitoring</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#version-pinning">Version Pinning</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#private-maven-repositories">Private Maven-Repositories</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#implementation-of-code-reviews-and-security-checks">Implementation of code reviews and security checks</a></li></ol></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#understanding-cves-in-the-context-of-maven-and-cache-poisoning">Understanding CVEs in the context of Maven and cache poisoning</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#relevant-cves-related-to-maven-and-repository-management-tools">Relevant CVEs related to Maven and repository management tools</a><ol><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#cve-2020-13949-remote-code-execution-in-apache-maven">CVE-2020-13949: Remote Code Execution in Apache Maven</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#cve-2021-25329-denial-of-service-in-maven-artifact-resolver">CVE-2021-25329: Denial of Service in Maven Artifact Resolver</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#cve-2019-0221-directory-traversal-in-sonatype-nexus-repository-manager">CVE-2019-0221: Directory Traversal in Sonatype Nexus Repository Manager</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#cve-2022-26134-arbitrary-file-upload-in-jfrog-artifactory">CVE-2022-26134: Arbitrary File Upload in JFrog Artifactory</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#cve-2021-44228-log4shell-log4j">CVE-2021-44228: Log4Shell (Log4j)</a></li></ol></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#analysis-and-impact-of-the-mentioned-cves">Analysis and impact of the mentioned CVEs</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#future-challenges-and-continuous-security-improvements">Future challenges and continuous security improvements</a></li><li><a href="https://svenruppert.com/2024/10/07/cache-poisoning-attacks-on-dependency-management-systems-like-maven/#conclusion">Conclusion</a></li></ol><h2 id="overview-of-maven-and-its-caches">Overview of Maven and its caches</h2><p>Apache Maven is a widely used build management tool in Java projects. It automates the dependency management, build process, and application deployment. However, some fundamental mechanisms make it necessary to consider the security of repositories and dependencies when using Maven.</p><p>Maven uses repositories to manage libraries and dependencies. There are two types of Maven repositories:</p><p><strong>Local repository</strong> : A copy of all downloaded libraries and dependencies is saved on the local machine.</p><p><strong>Remote Repositories</strong> : Maven can access various remote repositories, such as the central Maven repository or a company&rsquo;s custom repositories.</p><p>After downloading them from a remote repository, Maven stores all dependencies in the local repository (cache). This allows dependencies that are needed multiple times to load more quickly because they do not have to be downloaded from a remote repository each time.</p><h2 id="what-is-cache-poisoning">What is cache poisoning?</h2><p>Cache poisoning is a class of attacks in which an attacker fills a system&rsquo;s cache (in this case Maven caches) with manipulated or malicious content. This results in legitimate requests that should receive the original data instead of the data injected by an attacker. In Maven terms, cache poisoning refers to when an attacker injects malicious artefacts into a developer&rsquo;s or build&rsquo;s cache by exploiting a vulnerability in the Maven build process or repository servers.</p><p>The attack aims to deliver malicious dependencies that are then integrated into software projects. These poisoned dependencies could contain malicious code to steal sensitive data, take control of the system, or sabotage the project.</p><h2 id="types-of-cache-poisoning-on-maven-caches">Types of cache poisoning on Maven caches</h2><p>There are several scenarios in which cache poisoning attacks can be carried out on Maven repositories:</p><h3 id="man-in-the-middle-mitm-cache-poisoning">Man-in-the-Middle (MITM) Cache Poisoning</h3><p>A man-in-the-middle attack allows an attacker to intercept and manipulate network traffic between the developer and the remote Maven repository. If communication is not encrypted, an attacker can inject crafted artefacts and introduce them into the local Maven cache. As a result, the developer believes that the dependencies come from a trusted repository, when in fact, they have been tampered with.</p><p>Such an attack could be successful if Maven communicates with repositories over unsecured HTTP connections. The central Maven repository (Maven Central) now exclusively uses HTTPS to prevent such attacks, but some private or legacy repositories use HTTP.</p><h3 id="exploit-repository-vulnerabilities">Exploit repository vulnerabilities</h3><p>If an attacker gains access to the remote repository, they can upload arbitrary artefacts or replace existing versions. This happens, for example, if the repository is poorly secured or a vulnerability in the repository management tool (like Nexus or Artifactory) is exploited. In this case, the attacker can inject malware directly into the repository, causing developers worldwide to download the compromised artefact and store it in their Maven cache.</p><h3 id="dependency-confusion">Dependency Confusion</h3><p>A particularly dangerous attack vector that has received much attention in recent years is the so-called &ldquo;dependency confusion&rdquo; attack. This attack is because many modern software projects draw dependencies from internal and private repositories and public repositories such as Maven Central. The main goal of a Dependency Confusion attack is to inject malicious packages via publicly accessible repositories into a company or project that believes it is using internal or private dependencies.</p><h4 id="basics-of-dependency-confusion">Basics of Dependency Confusion</h4><p>Many companies and projects maintain internal Maven repositories where they store their own libraries and dependencies that are not publicly accessible. These internal libraries can implement specific functionalities or make adaptations to public libraries. Developers often define the name and version of dependencies in the Maven configuration (<code>pom.xml</code>) without realising that Maven prioritises dependencies, favouring public repositories like Maven Central over internal ones unless explicitly configured otherwise.</p><p>A dependency confusion attack exploits exactly this priority order. The attacker publishes a package with the same name as an internal library to the public Maven repository, often with a higher version number than the one used internally. When Maven then looks for that dependency, it usually prefers the publicly available package rather than the private internal version. This downloads the malicious package and stores it in the developer&rsquo;s Maven cache, from where it will be used in future builds.</p><h4 id="how-dependency-confusion-was-discovered">How Dependency Confusion Was Discovered</h4><p>A security researcher named Alex Birsan popularised this attack in 2021 when he demonstrated how easy it was to poison dependencies in projects at major tech companies. By releasing packages with the same names as internal libraries of large companies such as Apple, Microsoft, and Tesla, he successfully launched dependency confusion attacks against these companies.</p><p>Birsan did not use malicious content in his attacks but harmless code to prove that the system was vulnerable. He was able to show that in many cases, the companies&rsquo; build systems had downloaded and used the malicious (in his case harmless) package instead of the real internal library. This disclosure led to massive awareness in the security community about the risks of Dependency Confusion.</p><h4 id="why-does-dependency-confusion-work-so-effectively">Why does Dependency Confusion work so effectively?</h4><p>The success of a Dependency Confusion attack lies in the default configuration of many build systems and the way Maven resolves dependencies. There are several reasons why this attack vector is so effective:</p><ul><li>- Automatic prioritisation of public repositories</li><li>- Trust the version number</li><li>- Missing signature verification</li><li>- Reliance on external code</li></ul><h3 id="typosquatting">Typosquatting</h3><p>Typosquatting is an attack technique that exploits user oversight by targeting common typos that can occur while typing package names in software development, such as in Maven. Attackers release packages with similar or slightly misspelt names that closely resemble legitimate libraries. When developers accidentally enter the wrong package name in their dependency definitions or automated tools resolve these packages, they download the malicious package. Typosquatting is one of the most well-known attack methods for manipulating package managers, such as Maven, npm, PyPI, and others that host publicly available libraries.</p><h4 id="basics-of-typosquatting">Basics of typosquatting</h4><p>Typosquatting is based on the idea that users often make typos when entering commands or package names. Attackers exploit this by creating packages or artifacts with names that are very similar to well-known and widely used libraries but differ in small details. These details may include minor variations such as missing letters, additional characters, or alternative spellings.</p><h4 id="typical-typosquatting-techniques">Typical typosquatting techniques</h4><p><strong>Misspelled package names</strong> :</p><p>One of the most straightforward techniques is to change or add a letter in the name of a well-known library. An example would be the package<code>**com.google.common**</code>, which is often used. An attacker could use a package named<code>**com.gooogle.common**</code> (with an extra &ldquo;o&rdquo;) that is easily overlooked.</p><p><strong>Different spellings</strong> :</p><p>Attackers can also use alternative spellings of well-known libraries or names. For example, an attacker could use a package named<code>**com.apache.loggin**</code> to publish the popular<code>**com.apache.logging**</code> looks similar, but due to the missing letter combination &ldquo;<strong>g</strong> " and &ldquo;<strong>n</strong> " at &ldquo;<strong>logging</strong> " is easily overlooked.</p><p><strong>Use of prefixes or suffixes</strong> :</p><p>Another option is to add prefixes or suffixes that increase the similarity to legitimate packages. For example, an attacker could use the package<code>**com.google.common-utils**</code> or<code>**com.google. commonx</code> to publish** the same as the legitimate package<code>**com.google.common**</code> resembles.</p><p><strong>Similarity in naming</strong> :</p><p>Attackers can also take advantage of naming conventions in the open-source community by publishing packages containing common terms or abbreviations often used in combination with other libraries. An example would be releasing a package like<code>**common-lang3-utils**</code>, which is linked to the popular Apache Commons library `<strong>commons-lang3</strong> &rsquo; remembers.</p><h4 id="dangers-of-typosquatting">Dangers of typosquatting</h4><p>The threat of typosquatting is grave because it is difficult to detect. Developers often rely on their build tools like Maven to reliably download and integrate packages into their projects. If an incorrect package name is entered, you may not immediately realise that you have included a malicious dependency. Typosquatting is a form of social engineering because it exploits people&rsquo;s susceptibility to errors.</p><p>A successful typosquatting attack can lead to severe consequences:</p><ul><li>Data loss</li><li>Malware injection</li><li>Loss of trust</li></ul><h4 id="maven-typosquatting-cases">Maven typosquatting cases</h4><p>There have also been incidents of typosquatting in the Maven community. In one case, a package named<code>**commons-loggin**</code> was published, corresponding to the legitimate Apache Commons logging package<code>**commons-logging**</code>. Developers who entered the package name incorrectly downloaded and integrated the malicious package into their projects, creating potential security risks.</p><p>Typosquatting is a sophisticated and difficult-to-detect attack method that targets human error. Attackers take advantage of the widespread use of package managers such as Maven, npm, and PyPI by publishing slightly misspelt or similar-sounding packages that contain malicious code. Developers and organisations must be aware of this threat and take appropriate protective measures to ensure that only legitimate and trustworthy packages are included in their projects.</p><h2 id="process-of-a-cache-poisoning-attack-on-maven">Process of a cache poisoning attack on Maven</h2><p>A typical sequence of a cache poisoning attack on Maven could look like this:</p><p><strong>Identification of a target repository</strong> : The attacker is looking for a Maven repository used by developers but may have vulnerabilities. This can happen, for example, through outdated versions of publicly available repository management tools.</p><p><strong>Handling Artifacts</strong> : The attacker manipulates the artefact, e.g. a JAR file, by adding malicious code. This can range from simple backdoors to complex Trojans.</p><p><strong>Provision of the poisoned artefact</strong> : The manipulated artefact is either uploaded to the public repository (e.g., in the form of a typosquatting package) or injected directly into a compromised target repository.</p><p><strong>Download by developer</strong> : The developer uses Maven to update or reload the dependencies for his project. Maven downloads the poisoned artefact, which is stored in the local cache.</p><p><strong>Compromising the project</strong> : Maven will use the poisoned artefact from the cache in future builds. This artefact can then execute malicious code in the application&rsquo;s context, resulting in system compromise.</p><h2 id="security-mechanisms-to-protect-against-cache-poisoning">Security mechanisms to protect against cache poisoning</h2><p>Various measures should be implemented on both the developer and repository provider sides to protect against cache poisoning attacks.</p><h4 id="regular-updates-and-patch-management">Regular updates and patch management</h4><p>Make sure Maven, its plugins, and all repository management tools are always up to date. Security updates should be applied immediately to address known vulnerabilities.</p><h4 id="using-https">Using HTTPS</h4><p>The use of encrypted connections (HTTPS) between Maven and the repository is crucial to ensure that no man-in-the-middle attacks can be performed on the transferring artifacts. Maven Central enforces HTTPS connections, but private repositories should also adhere to this standard.</p><h4 id="signature-verification">Signature verification</h4><p>Another protective measure is the use of cryptographic signatures for artefacts. Maven supports the use of PGP signatures to ensure the integrity of artefacts. Developers should ensure that the signatures of downloaded artefacts are verified to ensure that they have not been tampered with.</p><h4 id="improve-repository-security">Improve repository security</h4><p>Repository providers should ensure that their repositories are well protected by implementing robust authentication mechanisms, regular patches and updates to repository management tools such as Nexus or Artifactory.</p><h4 id="dependency-scanning-and-monitoring">Dependency Scanning and Monitoring</h4><p>Tools like OWASP Dependency Check or Snyk can scan known dependency vulnerabilities. These tools can help identify malicious or stale dependencies and prevent them from entering the Maven cache.</p><h4 id="version-pinning">Version Pinning</h4><p>&ldquo;Version pinning&rdquo; means setting specific versions of dependencies in the<code>pom.xml</code> file instead of using dynamic version ranges (<code>[1.0,)</code>). This helps prevent unexpected updates and ensures that only explicitly defined versions of the artefacts are used.</p><h4 id="private-maven-repositories">Private Maven-Repositories</h4><p>One approach to maintaining control over dependencies is maintaining a private Maven repository within the organisation. This ensures that only checked artifacts end up in the internal cache, reducing the risk of introducing malicious dependencies into the build process.</p><h4 id="implementation-of-code-reviews-and-security-checks">Implementation of code reviews and security checks</h4><p>Conduct regular code reviews to ensure only trusted dependencies are used in projects. Automated security checks can provide additional security.</p><h2 id="understanding-cves-in-the-context-of-maven-and-cache-poisoning">Understanding CVEs in the context of Maven and cache poisoning</h2><p><strong>CVE (Common Vulnerabilities and Exposures)</strong> is a standardised system for identifying and cataloguing security vulnerabilities in software. Each CVE number refers to a specific vulnerability discovered and documented by security experts.</p><p>There are no specific CVEs that exclusively target cache poisoning in the context of Maven and cache poisoning. Instead, various vulnerabilities in Maven itself, its plugins, or the underlying repository management tools (such as Sonatype Nexus or JFrog Artifactory) can be indirectly exploited for cache poisoning attacks. These vulnerabilities could allow attackers to manipulate dependencies, compromise the integrity of downloads, or bypass Maven&rsquo;s security mechanisms.</p><h2 id="relevant-cves-related-to-maven-and-repository-management-tools">Relevant CVEs related to Maven and repository management tools</h2><p>Although no CVEs are explicitly classified as cache poisoning, there are several vulnerabilities in Maven and related tools that could potentially be exploited for cache poisoning attacks:</p><h3 id="cve-2020-13949-remote-code-execution-in-apache-maven">CVE-2020-13949: Remote Code Execution in Apache Maven</h3><ul><li><strong>Description</strong> : This vulnerability affected Maven Surefire plugin versions prior to 2.22.2, which could enable remote code execution (RCE). An attacker could use specially crafted POM files to execute malicious code on the build system.</li><li><strong>Relevance to cache poisoning</strong> : By running RCE, an attacker could inject crafted dependencies into the local Maven cache, which could compromise future builds.</li><li><strong>Reference</strong> :<a href="https://nvd.nist.gov/vuln/detail/CVE-2020-13949"> CVE-2020-13949</a></li></ul><h3 id="cve-2021-25329-denial-of-service-in-maven-artifact-resolver">CVE-2021-25329: Denial of Service in Maven Artifact Resolver</h3><ul><li><strong>Description</strong> : This vulnerability affected the Apache Maven Artifact Resolver component, which is responsible for resolving and downloading artefacts. A specially crafted POM file could lead to a denial of service (DoS).</li><li><strong>Relevance to cache poisoning</strong> : A DoS attack could impact the availability of Maven repositories, forcing developers to use alternative (possibly insecure) repositories, increasing the risk of cache poisoning.</li><li><strong>Reference</strong> :<a href="https://nvd.nist.gov/vuln/detail/CVE-2021-25329"> CVE-2021-25329</a></li></ul><h3 id="cve-2019-0221-directory-traversal-in-sonatype-nexus-repository-manager">CVE-2019-0221: Directory Traversal in Sonatype Nexus Repository Manager</h3><ul><li><strong>Description</strong> : This vulnerability allows attackers to access files within the Nexus Repository Manager through a directory traversal attack.</li><li><strong>Relevance to cache poisoning</strong> : By accessing critical files or configurations, attackers could compromise the repository manager and insert malicious artefacts, which are then downloaded by developers and stored in the local Maven cache.</li><li><strong>Reference</strong> :<a href="https://nvd.nist.gov/vuln/detail/CVE-2019-0221"> CVE-2019-0221</a></li></ul><h3 id="cve-2022-26134-arbitrary-file-upload-in-jfrog-artifactory">CVE-2022-26134: Arbitrary File Upload in JFrog Artifactory</h3><ul><li><strong>Description</strong> : This vulnerability allowed attackers to upload arbitrary files to a JFrog Artifactory server, which could result in a complete compromise of the server.</li><li><strong>Relevance to cache poisoning</strong> : By uploading arbitrary files, attackers could inject malicious Maven artefacts into the repository, which developers then download and cache.</li><li><strong>Reference</strong> :<a href="https://nvd.nist.gov/vuln/detail/CVE-2022-26134"> CVE-2022-26134</a></li></ul><h3 id="cve-2021-44228-log4shell-log4j">CVE-2021-44228: Log4Shell (Log4j)</h3><ul><li><strong>Description</strong> : This widespread vulnerability affected the Log4j library and allowed remote code execution by exploiting JNDI injections.</li><li><strong>Relevance to cache poisoning</strong> : Many Maven projects use Log4j as a dependency. A manipulated version of this dependency could enable RCE through cache poisoning.</li><li><strong>Reference</strong> :<a href="https://nvd.nist.gov/vuln/detail/CVE-2021-44228"> CVE-2021-44228</a></li></ul><h2 id="analysis-and-impact-of-the-mentioned-cves">Analysis and impact of the mentioned CVEs</h2><p>The above CVEs illustrate how vulnerabilities in Maven and related tools can potentially be exploited for cache poisoning and other types of attacks:</p><ul><li><strong>Remote Code Execution (RCE)</strong> : Vulnerabilities such as CVE-2020-13949 and CVE-2021-44228 allow attackers to execute malicious code on the build system. This can be used to inject manipulated dependencies into the local cache.</li><li><strong>Denial of Service (DoS)</strong> : CVE-2021-25329 demonstrates how attackers can impact the availability of Maven repositories. This may result in developers being forced to resort to alternative sources that may be unsafe.</li><li><strong>Directory Traversal and Arbitrary File Upload</strong> : CVE-2019-0221 and CVE-2022-26134 demonstrate how attackers can compromise repository management tools to upload malicious artefacts that developers then unknowingly use in their projects.</li></ul><h2 id="future-challenges-and-continuous-security-improvements">Future challenges and continuous security improvements</h2><p>As the use of open-source libraries in modern software development continues to increase, the threat of attacks such as cache poisoning also increases. Automating the build process and relying on external repositories creates an expanded attack surface that attackers seek to exploit.</p><p>It is becoming increasingly important for companies and developers to cultivate a security culture prioritising the safe handling of dependencies and artefacts. This requires not only the implementation of technical protection measures but also the training of developers to raise awareness of the risks of cache poisoning and other dependency attacks.</p><h2 id="conclusion">Conclusion</h2><p>Cache poisoning attacks on Maven caches are a severe risk, especially at a time when open-source components play an essential role in software development. The attacks exploit vulnerabilities in how dependencies are resolved, downloaded and cached.</p><p>Developers and companies must implement best security practices to prevent such attacks. This includes using HTTPS, signing and verifying artefacts, securing repositories, regular vulnerability scanning, and controlling the dependencies introduced into their projects.</p><p>Awareness of the risks and implementing appropriate security measures are key to preventing cache poisoning attacks and ensuring the integrity of software development processes.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Security</category><category>Tools</category><category>Uncategorized</category><media:content url="https://svenruppert.com/images/2024/11/10E6CCCF-A86A-4682-910F-17BC01541B86.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/11/10E6CCCF-A86A-4682-910F-17BC01541B86.jpg"/><enclosure url="https://svenruppert.com/images/2024/11/10E6CCCF-A86A-4682-910F-17BC01541B86.jpg" type="image/jpeg" length="0"/></item><item><title>JUnit annotations in focus: The connection between @Test and @Testable</title><link>https://svenruppert.com/posts/junit-annotations-in-focus-the-connection-between-test-and-testable/</link><pubDate>Fri, 08 Nov 2024 09:12:39 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/junit-annotations-in-focus-the-connection-between-test-and-testable/</guid><description>The annotations @Test and @Testable have played an important role in the Java ecosystem regarding application testing. They are essential tools that help developers make unit and automated testing more effective. In this paper, we will explore the differences and connections between @Test and @Testable analyze and the motivation behind the introduction of @Testable understand. We will also play the role of @Testable in developing your test engines and discuss their importance for the flexibility and expandability of tests.</description><content:encoded>&lt;![CDATA[<p>The annotations @Test and @Testable have played an important role in the Java ecosystem regarding application testing. They are essential tools that help developers make unit and automated testing more effective. In this paper, we will explore the differences and connections between @Test and @Testable analyze and the motivation behind the introduction of @Testable understand. We will also play the role of @Testable in developing your test engines and discuss their importance for the flexibility and expandability of tests.</p><ol><li><a href="https://svenruppert.com/2024/11/08/auto-draft/#test-and-their-usage">@Test and their usage</a></li><li><a href="https://svenruppert.com/2024/11/08/auto-draft/#testable-what-is-it-for">@Testable - What is it for?</a></li><li><a href="https://svenruppert.com/2024/11/08/auto-draft/#the-connection-between-test-and-testable">The connection between @Test and @Testable</a></li><li><a href="https://svenruppert.com/2024/11/08/auto-draft/#the-role-of-testable-for-developing-your-own-testengines">The role of @Testable for developing your own TestEngines</a></li><li><a href="https://svenruppert.com/2024/11/08/auto-draft/#conclusion">Conclusion</a></li></ol><h3 id="test-and-their-usage">@Test and their usage</h3><p>Die Annotation @Test is familiar to most Java developers involved in unit testing. It comes from the JUnit framework, one of Java&rsquo;s most commonly used frameworks for automated testing. @Test is used to identify methods as test methods that are then executed by the testing framework. When using JUnit 4 or 5, developers mark a method with @Testto, explicitly indicating that this method represents a test method to be tested. The JUnit framework takes over the execution of these methods and evaluates whether the expected conditions are met or errors occur. The main advantage of using @Test is that the framework automatically recognises the test methods and reports their results, including errors or successful runs. This makes maintaining and expanding tests easier, as all @Test-annotated methods can be executed without manual configuration.</p><p>An example of a simple test method looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.junit.jupiter.api.Test</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">org.junit.jupiter.api.Assertions.assertEquals</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">CalculatorTest</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Test</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">void</span><span class="w"/><span class="nf">testAddition</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Calculator</span><span class="w"/><span class="n">calc</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Calculator</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">assertEquals</span><span class="p">(</span><span class="n">5</span><span class="p">,</span><span class="w"/><span class="n">calc</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">2</span><span class="p">,</span><span class="w"/><span class="n">3</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the method testAddition is paired with @Test, so the JUnit framework recognises it as a test method. Executing the test checks whether the addition works correctly. The goal is to ensure that the tested code exhibits the desired behaviour, which increases the application&rsquo;s reliability.</p><p>The use of @Test makes the testing process easy and efficient. Developers can create many test methods automatically recognised and executed by the JUnit framework. This is particularly useful in large projects where test coverage is essential in ensuring that the codebase remains stable and bug-free. @Test allows developers to quickly receive feedback on the health of their applications, resulting in shorter development cycles and improved software quality.</p><h3 id="testable---what-is-it-for">@Testable - What is it for?</h3><p>@Testable comes from the package<strong>org.junit.platform.commons.annotation</strong> and was developed specifically for the JUnit 5 platform. The annotation @Testable is used to mark test methods that should be recognised by runtime environments such as test engines. The annotation @Test in JUnit 5 is a kind of special case @Testable-Annotation. In JUnit 5 is @Test yourself with @Testable annotated. This means that everyone with @Test marked method implicitly to @Testable is what ensures detection by testing engines. The use of @Testable in the annotation @Test ensures that all JUnit testing methods are automatically accessible to the testing platform without the need for additional annotations. This increases testability in different test environments and supports the modularity of JUnit 5.</p><p>The introduction of @Testable brought more flexibility to the test system. While @Test mainly specifies the concrete implementation of the test, @Testable is a common annotation that enables other testing engines to discover and execute test methods dynamically. This is particularly useful for facilitating integration between different testing tools and improving the extensibility of the JUnit 5 framework. Developers who need to perform different types of testing in a project benefit from the flexibility of @Testable because it enables different test engines to work together.</p><p>An example of using @Testable is rarely found at the developer level because the JUnit engine @Testable is used internally by all, with @Test automatically marking annotated methods as testable. This means developers usually don&rsquo;t explicitly care about the annotation @Testable, as the runtime environment and the test engine handle this. This abstraction is advantageous because it saves developers the effort of explicitly ensuring their testing methods are discoverable - the platform does that for them.</p><h3 id="the-connection-between-test-and-testable">The connection between @Test and @Testable</h3><p>The main difference between @Test and @Testable lies in their application and purpose. While @Test is the specific testing method for the JUnit framework @Testable a general meta-annotation that helps test runtime environments better recognise and process test methods. @Testable provides an abstraction layer that ensures that any method marked as a test is also available to the testing engine. This mainly helps when developing frameworks and tools based on the JUnit platform.</p><p>JUnit 5 is a significant evolution compared to JUnit 4. It uses a more modular architecture and a testing engine called the &ldquo;JUnit Platform&rdquo;. This platform is designed to allow tests to be executed more flexibly so that other testing frameworks can also use the platform. @Testable plays an important role in this context as it ensures the visibility of the tests on the platform. Developers benefit from a simplified integration of test tools, as the platform contains all tests @Testable that are annotated, recognised and executed regardless of the test framework used.</p><p>The introduction of @Testable is, therefore, in the context of JUnit 5&rsquo;s extensibility and flexibility. While JUnit 4 was primarily designed for unit testing, JUnit 5 enables better integration with other testing and tools. This makes the testing infrastructure for modern Java applications more powerful and adaptable. Using different testing engines and frameworks on the same platform significantly improves application testability and maintainability.</p><h3 id="the-role-of-testable-for-developing-your-own-testengines">The role of @Testable for developing your own TestEngines</h3><p>Die Annotation @Testable is crucial for developers who want to develop their testing engines. It provides a standardised way to mark test methods for the JUnit Platform, meaning that test engines compatible with it can recognise and execute test methods without making any special customisations. Using @Testable, Developers can ensure that their tests are recognised regardless of the testing framework used, promoting interoperability and extensibility of test engines.</p><p>The JUnit Platform is designed to support various testing engines, significantly increasing developers&rsquo; flexibility. @Testable provides a type of &ldquo;contract&rdquo; that allows the JUnit Platform to identify and execute tests, whether they were built using JUnit, TestNG, or a custom testing engine. Developers can write their own test engines seamlessly integrated into the JUnit Platform and benefit from the existing functionalities. This does @Testable, a central building block for the development of an expandable and modular test environment.</p><p>Another advantage of using @Testable for your own test engines is the standardisation of the test process. By labelling all test methods with @Testable, Developers can ensure that their tests run consistently across different projects and teams. This not only promotes the reusability of test engines but also the ability to develop new test engines that meet specific requirements without disrupting the fundamental architecture of the JUnit Platform.</p><p>With @Testable It also becomes possible to advance the development of specialized testing engines optimized for specific types of testing. For example, performance testing, security testing or end-to-end testing could be carried out by purpose-built engines based on the JUnit Platform. This creates a modular and versatile testing environment that allows the best tools to be used for different testing needs without compromising integration and compatibility.</p><h3 id="conclusion">Conclusion</h3><p>The annotations @Test and @Testable play different but complementary roles in the Java testing ecosystem. @Test identifies specific testing methods for JUnit, while @Testable ensures that these methods are accessible to testing engines on a more general level. The introduction of @Testable as part of JUnit 5 offers greater flexibility and makes it easier to expand the testing system, which meets the modern requirements for testing in the Java world.</p><p>Overall, both annotations show the continuous development of testing tools in Java to meet the growing requirements for maintainability, extensibility and integration. The introduction of @Testable highlights the trend towards a more flexible and expandable testing infrastructure that makes it possible to combine different test types and engines in a common platform. This allows developers to make testing more efficient and test their applications comprehensively and reliably. By combining @Test and @Testable, A robust test basis is created that supports the development process and sustainably improves the quality of software products.</p>
]]></content:encoded><category>Java</category><category>JUnit5</category><category>TDD</category><category>Tools</category><media:content url="https://svenruppert.com/images/2024/11/DALL%C2%B7E-2024-11-08-09.09.38-A-conceptual-illustration-representing-Java-annotation-@Testable-in-a-wooden-style-featuring-abstract-symbols-of-testing-software-code-and-modular.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/11/DALL%C2%B7E-2024-11-08-09.09.38-A-conceptual-illustration-representing-Java-annotation-@Testable-in-a-wooden-style-featuring-abstract-symbols-of-testing-software-code-and-modular.jpeg"/><enclosure url="https://svenruppert.com/images/2024/11/DALL%C2%B7E-2024-11-08-09.09.38-A-conceptual-illustration-representing-Java-annotation-@Testable-in-a-wooden-style-featuring-abstract-symbols-of-testing-software-code-and-modular.jpeg" type="image/jpeg" length="0"/></item><item><title>The Risks of Mocking Frameworks: How Too Much Mocking Leads to Unrealistic Tests</title><link>https://svenruppert.com/posts/the-risks-of-mocking-frameworks-how-too-much-mocking-leads-to-unrealistic-tests/</link><pubDate>Tue, 05 Nov 2024 17:49:33 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/the-risks-of-mocking-frameworks-how-too-much-mocking-leads-to-unrealistic-tests/</guid><description>Extensive use of mocking frameworks such as Mockito in software development can lead to unrealistic tests. This is because mocking frameworks simulate dependencies of classes or methods in order to test them in isolation. However, when too many mock objects are used, the test often loses touch with reality, which can affect the validity and reliability of the tests. It is important to use mocking carefully to find the right balance between isolated testing and realistic simulation.</description><content:encoded>&lt;![CDATA[<p>Extensive use of mocking frameworks such as Mockito in software development can lead to unrealistic tests. This is because mocking frameworks simulate dependencies of classes or methods in order to test them in isolation. However, when too many mock objects are used, the test often loses touch with reality, which can affect the validity and reliability of the tests. It is important to use mocking carefully to find the right balance between isolated testing and realistic simulation.</p><p>A mock is a simulated object that imitates the behaviour of a real component without executing any actual logic. This is particularly useful when a dependency is difficult to test, for example, because it involves an external database or web service that may have states that are difficult to reproduce or where testing would be very time-consuming and costly. Mocking creates a simplified and manageable version of this dependency, allowing targeted and isolated unit testing to be carried out. Mocking can be a precious technique in these cases, especially for writing independent tests that do not require external infrastructure. It reduces the complexity of tests and makes them faster and more deterministic.</p><p>However, it becomes problematic when the logic of the tested class is heavily influenced by these dependencies, especially when these dependencies have complex logic or dynamic behaviour. For example, a class that reads data from a database might implement logic that responds to the available data contents. This dynamic nature often cannot be realistically replicated with mocks, as mock objects are usually limited to predefined, simple return values. As a result, the test can lose touch with the objective complexity and only inadequately reflect the actual application logic. Realistic tests are created that only consider some possible application behaviours in interaction with other components, resulting in incomplete test coverage.</p><p>Extensive use of mocks often results in tests being tailored very specifically to the simulated scenarios. In such cases, the tests become helpful only for the implemented interfaces and the programmed code rather than for the actual use cases, which may contain a variety of unexpected events and uncontrolled factors. This can lead to a false sense of security that the application is functioning correctly, even though essential real-world conditions still need to be considered. For example, network failures, errors in data transmission or inconsistent data sets could not be simulated in the mocks because the tests focus only on positive confirmation of the function. This means that critical errors that could occur in a natural environment are missed, leading to severe problems in the production environment. To combat this, mocking should be used judiciously, and tests should also be performed that incorporate real-world dependencies to ensure that the code works correctly under real-world conditions such as network problems, database failures, or changing data.</p><p>A concrete example of this would be an application that retrieves data from a REST API and processes it. In a typical scenario, this REST API could return different data sets, ranging from simple JSON responses to complex, nested structures. When you replace the REST API with a mock object, you only simulate a specific reaction, which is highly simplified and mostly represents the &lsquo;happy path&rsquo;. In reality, however, the REST API could react in various ways: data could be missing, the data structure could change, new fields could be added, or network problems and unexpected error messages could occur. Additionally, errors such as receiving an empty response or submitting an HTTP error (e.g. 500 or 404) are often problematic to represent when only a mock is used. Tests based only on mocks cannot adequately simulate these realities because they need to consider the multitude of possible scenarios and failure cases that can arise in actual operation. This leads to a false sense of security about the robustness of the code. If the code is only tested in a strictly controlled environment, without considering the real variability of the API and its potential failures, serious errors may appear in the production environment that were never previously detected. Using accurate integration tests that respond to actual API responses and errors would be much more realistic in this case and would help ensure application robustness under real-world conditions.</p><p>Another problem arises when the extensive use of mocks oversimplifies the dependencies. For example, consider an e-commerce application that performs inventory checking. In a real-world implementation, this inventory check can involve a variety of complex operations, such as synchronising inventory across multiple geographically distributed warehouses, honouring reservations for ongoing orders, or updating inventory in real-time. These tasks require sophisticated logic, potentially involving numerous systems and technologies, to ensure inventories are always accurate. When you fully mock a warehouse management class, these complex dependencies are replaced with simple, simulated return values. However, this means that the test loses connection to the real functioning of the system. There is no guarantee that synchronisation logic will work smoothly under real-world conditions, such as network delays or inconsistencies between warehouses. The complexity of the real system is never taken into account in the tests, so possible sources of error remain undetected. In a production environment, unforeseen problems could arise that could have been avoided with a more realistic test environment. These complex scenarios show that real integration tests are necessary to ensure that the interaction between the components works correctly, even under load conditions or in the event of errors in the dependencies.</p><p>Another problem arises when test implementations are created too specifically for the mock scenarios. It often happens that developers unconsciously tailor the mock configuration exactly to the logic being tested without taking into account the realistic spread of the data or the potential errors. This results in the tests not reflecting unforeseen events that may occur in a real environment. An example of this is a database query where the test always assumes a successful query because the mock was configured that way. The realistic error case of a database failure, a zero response or a timeout is not practised. This means that the corresponding error handling is noticed in the production environment once it has already had an impact, which can lead to critical problems. Such gaps in error handling could be avoided if tests included a more realistic and varied simulation of possible scenarios. Therefore, tests must also cover hostile and unpredictable scenarios to ensure the application is robust enough to handle errors and exceptions.</p><p>Another problem when using mocking frameworks is that the functionality of the mocks only sometimes follows the actual implementation. Often, a simplified or even incorrect replica of the real components is created, which only reflects some aspects of real behaviour. For example, when replicating an external service using a mock, certain side effects, state changes, or dependencies in the real implementation cannot be adequately considered. This can cause the test to pass even though the actual implementation would fail due to its complexity.</p><p>Let&rsquo;s take an example of a Java application that accesses an external cache service to cache data. Suppose we have a class<code>ProductService</code> that reads product information from a database and stores it in a cache to improve performance. In a unit test, the cache service method could easily be mocked to ensure the<code>put()</code> call runs correctly. The corresponding Java code could look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">org.mockito.Mockito.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ProductServiceTest</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Test</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">testFetchProduct</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">CacheService</span><span class="w"/><span class="n">cacheService</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">mock</span><span class="p">(</span><span class="n">CacheService</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">DatabaseService</span><span class="w"/><span class="n">databaseService</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">mock</span><span class="p">(</span><span class="n">DatabaseService</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">ProductService</span><span class="w"/><span class="n">productService</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ProductService</span><span class="p">(</span><span class="n">databaseService</span><span class="p">,</span><span class="w"/><span class="n">cacheService</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Product</span><span class="w"/><span class="n">product</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Product</span><span class="p">(</span><span class="s">"123"</span><span class="p">,</span><span class="w"/><span class="s">"TestProduct"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">when</span><span class="p">(</span><span class="n">databaseService</span><span class="p">.</span><span class="na">getProduct</span><span class="p">(</span><span class="s">"123"</span><span class="p">)).</span><span class="na">thenReturn</span><span class="p">(</span><span class="n">product</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">productService</span><span class="p">.</span><span class="na">fetchProduct</span><span class="p">(</span><span class="s">"123"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">verify</span><span class="p">(</span><span class="n">cacheService</span><span class="p">,</span><span class="w"/><span class="n">times</span><span class="p">(</span><span class="n">1</span><span class="p">)).</span><span class="na">put</span><span class="p">(</span><span class="s">"123"</span><span class="p">,</span><span class="w"/><span class="n">product</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This test verifies that the product is cached correctly. However, it is not tested here whether the cache mechanism really works as desired, e.g., whether the cache is actually used for repeated requests to avoid unnecessary database queries. Such an error could go undetected in production if, in reality, the cache service is not functioning properly due to a synchronisation issue or misconfiguration. In addition, concurrent accesses to the cache could lead to synchronisation problems that need to be taken into account by simple mocking.</p><p>This test, therefore, provides no guarantee that the cache logic will work correctly under real-world conditions such as high load or concurrent accesses. The mocks only represent a simplified version of reality without taking into account all the possible scenarios that could occur in a real environment. Therefore, in addition to unit tests that use mocks, it is crucial also to perform integration tests that ensure that the cache service works correctly under realistic conditions. A classic example would be a component that internally uses caching mechanisms to minimise the number of requests to an external service. A simple mock could ignore the caching mechanism, so the test only checks whether a request is sent correctly to the external service, but not whether the caching logic works as intended. This allows cache management bugs or inefficient implementations to go undetected. Mocks also often do not realistically represent competing accesses to a resource, which could lead to synchronisation problems in the real implementation. Therefore, it is essential to ensure that mocks are as close as possible to the actual implementation to ensure realistic testing scenarios.</p><p>An alternative to mocking is integration or end-to-end testing, which tests the actual components and dependencies. Integration testing helps ensure that the application&rsquo;s various modules interact with each other as expected, while end-to-end testing verifies the overall system under realistic conditions. This does not use mocks but rather real databases, REST APIs and other services, which better simulate the complexity of the real environment.</p><p>Another alternative is so-called &lsquo;contract testing&rsquo;. Contract testing checks whether communication between two services occurs according to a defined specification. This allows both sides of the communication to be developed and tested independently without simulating the entire service. This ensures a certain level of security that both components work correctly with each other without resorting to extensive mocking.</p><p>In addition, test containers or in-memory databases can be used as alternatives to mocks, especially for database testing. Using tools like Testcontainers, real database instances can be deployed in Docker containers for testing so that real database logic is tested. In-memory databases like H2 in Java are also useful for making testing more realistic because they simulate actual data logic but without the cost and complexity of a real database connection.</p><p>Another alternative method is the use of so-called &lsquo;fake&rsquo; objects. Unlike mocks, which only simulate behaviour, fakes implement a reduced version of the real logic. These fakes are not as complex as the real components but still offer more realism than simple mocks. An example of this would be a &lsquo;fake&rsquo; database object that mimics basic storage operations in a simple data structure rather than actually connecting to a database. Fakes are particularly useful when you need a realistic but not fully functional, environment to test certain aspects of the application. They are more flexible than mocks and provide the ability to run through different scenarios without simulating the full complexity of real implementations.</p><p>In summary, excessive use of mocking frameworks like Mockito often results in tests not being realistic. The isolation of components enabled by mocking is useful for performing isolated unit testing. However, if too many aspects of the application are simulated, the tests lose the ability to represent the interaction of real components and the actual challenges that might arise in operation. There is then a risk that the software in real operation will not show the behaviour that the tests suggest. It, therefore makes sense to find a balance between mocking and integration tests in order to represent realistic scenarios adequately. At the same time, other techniques, such as contract testing or the use of fakes, should also be considered to ensure that the tests replicate the real environment as closely as possible, thus ensuring a robust and reliable application.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>TDD</category><media:content url="https://svenruppert.com/images/2024/11/DALL%C2%B7E-2024-11-05-17.46.36-A-conceptual-representation-of-software-testing-with-Mocking-frameworks-in-a-wooden-style.-A-digital-scene-featuring-interconnected-nodes-and-mock-obj.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/11/DALL%C2%B7E-2024-11-05-17.46.36-A-conceptual-representation-of-software-testing-with-Mocking-frameworks-in-a-wooden-style.-A-digital-scene-featuring-interconnected-nodes-and-mock-obj.jpeg"/><enclosure url="https://svenruppert.com/images/2024/11/DALL%C2%B7E-2024-11-05-17.46.36-A-conceptual-representation-of-software-testing-with-Mocking-frameworks-in-a-wooden-style.-A-digital-scene-featuring-interconnected-nodes-and-mock-obj.jpeg" type="image/jpeg" length="0"/></item><item><title>What is CWE-1007: Insufficient visual discrimination of homoglyphs for you as a user?</title><link>https://svenruppert.com/posts/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/</link><pubDate>Mon, 04 Nov 2024 12:34:27 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/</guid><description>The world of cybersecurity is full of threats, many of which are surprisingly subtle and challenging to detect. One such threat is the problem of so-called homoglyphs. CWE-1007, also known as &ldquo;Insufficient Visual Distinction of Homoglyphs Presented to User&rdquo;, is a vulnerability often used by attackers to deceive and compromise your systems or data. In this blog article, you will get a deep insight into CWE-1007, understand its mechanisms, and how to protect yourself from such attacks. We will discuss examples, technical challenges, and best practices that can help you as a developer understand and mitigate this threat.</description><content:encoded>&lt;![CDATA[<p>The world of cybersecurity is full of threats, many of which are surprisingly subtle and challenging to detect. One such threat is the problem of so-called homoglyphs. CWE-1007, also known as &ldquo;Insufficient Visual Distinction of Homoglyphs Presented to User&rdquo;, is a vulnerability often used by attackers to deceive and compromise your systems or data. In this blog article, you will get a deep insight into CWE-1007, understand its mechanisms, and how to protect yourself from such attacks. We will discuss examples, technical challenges, and best practices that can help you as a developer understand and mitigate this threat.</p><ol><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#what-are-homoglyphs">What are Homoglyphs?</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#cwe-1007-a-detailed-investigation">CWE-1007: A detailed investigation</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#examples-for-cwe-1007">Examples for CWE-1007</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#why-is-cwe-1007-dangerous">Why is CWE-1007 dangerous?</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#possible-attack-scenarios">Possible attack scenarios</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#defence-strategies-against-cwe-1007">Defence strategies against CWE-1007</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#technical-challenges-in-homoglyph-recognition">Technical challenges in homoglyph recognition</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#best-practices-for-developers">Best practices for developers</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#case-study-homoglyph-domain-attack">Case Study: Homoglyph Domain Attack</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#homoglyph-recognition-tools">Homoglyph recognition tools</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#which-cwes-are-often-used-in-conjunction-with-cwe-1007">Which CWEs are often used in conjunction with CWE-1007?</a>
1.<a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#cwe-601-open-redirect">CWE-601: Open Redirect:</a>
2.<a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#cwe-643-improper-neutralization-of-data-within-xpath-expressions">CWE-643: Improper Neutralization of Data within XPath Expressions:</a>
3.<a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#cwe-79-cross-site-scripting-xss">CWE-79: Cross-Site Scripting (XSS):</a>
4.<a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#cwe-20-improper-input-validation">CWE-20: Improper Input Validation:</a>
5.<a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#cwe-77-command-injection">CWE-77: Command Injection:</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#example-application-in-vaadin-flow">Example application in Vaadin Flow</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#conclusion">Conclusion</a></li><li><a href="https://svenruppert.com/2024/10/23/what-is-cwe-1007-insufficient-visual-discrimination-of-homoglyphs-for-you-as-a-user/#next-steps">Next Steps</a></li></ol><h3 id="what-are-homoglyphs">What are Homoglyphs?</h3><p>Before we delve into CWE-1007, we must understand what homoglyphs are. Homoglyphs are characters that look visually similar but represent different Unicode codes. This can involve different letters, numbers or symbols. A well-known example is the Latin letter &ldquo;O&rdquo; and the number &ldquo;0&rdquo;, which can look almost identical to the human eye. There are many other examples, such as &ldquo;l&rdquo; (small L) and &ldquo;I&rdquo; (capital i), or Cyrillic letters that look like Latin letters.</p><p>The visual similarity of homoglyphs is often exploited to deceive you. Attackers use such characters to create phishing websites, generate fake URLs, or corrupt code to make you believe you are dealing with a trustworthy resource. This is particularly problematic because we humans use visual patterns to make quick decisions and can more easily fall victim to such deceptions.</p><h3 id="cwe-1007-a-detailed-investigation">CWE-1007: A detailed investigation</h3><p>CWE-1007 refers to the insufficient visual discrimination of homoglyphs when presented as a user. If a system cannot distinguish between similar-looking characters or alert you to them, this can lead to significant security risks. You could accidentally click on a malicious link, visit a fake domain, or trust a fraudulent command.</p><p>This vulnerability is particularly common when displaying URLs, usernames, or commands. For example, you could click on a URL that appears to be correct but actually uses homoglyphs to represent a fake website. This allows attackers to steal passwords, credit card information, or other sensitive data. This problem is compounded by widespread internet use, as you constantly interact with potentially harmful content.</p><h3 id="examples-for-cwe-1007">Examples for CWE-1007</h3><p>A typical example of this vulnerability is the use of fake domains that use homoglyphs. Let&rsquo;s say you receive an email with a link to &ldquo;paypa1.com&rdquo; (which uses the number &ldquo;1&rdquo; instead of the letter &ldquo;l&rdquo;). Without careful consideration, you might think this is a legitimate link to the PayPal website. The same principle can also apply to usernames on social networks or even essential commands in a console.</p><p>Another example is the use of homoglyphs in source code. Attackers could insert fake characters into the code that look like legal characters but result in different functionality. This can be particularly dangerous in open-source projects or teams where multiple developers work on the same code and could miss similar characters. This leads to security vulnerabilities that can be exploited for attacks and poses a high risk to the integrity of the code.</p><p>An everyday example is a fake username on social networks. An attacker could create a username like &ldquo;facebook_support&rdquo; using the Cyrillic &ldquo;е&rdquo;, which looks visually similar to the Latin &ldquo;e&rdquo;. You might think it&rsquo;s an official support channel and click on malicious links or reveal sensitive data.</p><h3 id="why-is-cwe-1007-dangerous">Why is CWE-1007 dangerous?</h3><p>CWE-1007 is dangerous because humans rely heavily on visual cues to make decisions. The human eye is trained to recognise patterns and process information quickly, but often without closely examining each character. Attackers can specifically exploit this weakness to deceive you.</p><p>The danger of this vulnerability is not only that you could fall for phishing attacks. It can also lead to more severe security breaches, such as stealing credentials, tampering with software, or inserting malicious code into seemingly legitimate projects. In addition, this deception can also cause financial damage because you trust fake websites and enter your payment information, which can then be misused.</p><p>Another danger is that companies can lose their reputation due to these vulnerabilities. If you repeatedly fall for counterfeit versions of a well-known brand, your trust in that brand can be severely affected. Attackers use this method to undermine user trust and specifically to maximise damage to companies.</p><h3 id="possible-attack-scenarios">Possible attack scenarios</h3><p><strong>Phishing attacks using fake URLs</strong> : Attackers can create a URL almost identical to a known website. You click on the link without noticing that the domain has a slight change (e.g. a Cyrillic letter instead of a Latin letter). This allows attackers to access sensitive information such as passwords or credit card details.</p><p><strong>Code injection through fake characters</strong> : In complex software projects, homoglyphs can cause the code to execute differently than it seems at first glance. You or other developers may miss these signs, resulting in hard-to-find vulnerabilities in your code. This could be used to insert malicious functions only noticeable in the production environment.</p><p><strong>Social engineering in social networks</strong> : An attacker could create a username almost identical to a trusted contact&rsquo;s to deceive you and steal information. For example, someone could use the name &ldquo;LinkedIn Support&rdquo; with a slightly modified letter to trick you into giving up your login information or clicking on malicious links. The deception can be particularly effective if you don&rsquo;t carry out additional security checks.</p><h3 id="defence-strategies-against-cwe-1007">Defence strategies against CWE-1007</h3><p>To protect you from the threat of CWE-1007, technical and organisational measures are necessary. Here are some strategies that can help you minimise risk:</p><p><strong>Unicode normalisation</strong> : One of the most effective methods of recognising homoglyphs is Unicode normalisation. Unicode normalisation converts similar-looking characters into standard forms, making identifying them more accessible. This can prevent different writing systems from being used to deceive.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.text.Normalizer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">UnicodeNormalizationExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">String</span><span class="w"/><span class="n">suspiciousString</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"päypäl.com"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">String</span><span class="w"/><span class="n">normalizedString</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Normalizer</span><span class="p">.</span><span class="na">normalize</span><span class="p">(</span><span class="n">suspiciousString</span><span class="p">,</span><span class="w"/><span class="n">Normalizer</span><span class="p">.</span><span class="na">Form</span><span class="p">.</span><span class="na">NFKC</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Normalized String: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">normalizedString</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span></span></span></code></pre></div></div><p>This example shows how characters can be normalised to ensure that visually similar but different characters are recognised as such.</p><p><strong>User training</strong> : Training in dealing with suspicious emails, links and domain names is an essential line of defence. You should learn to check URLs carefully and be aware that characters from different writing systems can be used to deceive you. This training should include regular exercises and examples to increase awareness and improve your ability to recognise such attacks.</p><p><strong>Security warnings in the browser</strong> : Modern browsers have mechanisms to warn you if you visit a suspicious domain or use characters from different Unicode writing systems. These warnings should be enabled to protect you from such threats. Browser extension developers could develop additional filtering mechanisms that promptly alert you to possible deception.</p><p><strong>Code reviews and static code analysis tools</strong> : In software projects, developers should conduct code reviews to identify suspicious characters. Static code analysis tools can also help detect homoglyphs in code and mitigate potential security risks.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">CodeReviewExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">containsSuspiciousCharacters</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">input</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="c1">// Checks whether the string contains non-Latin characters</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">return</span><span class="w"/><span class="o">!</span><span class="n">input</span><span class="p">.</span><span class="na">matches</span><span class="p">(</span><span class="s">"^[\ -\~]*$"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">String</span><span class="w"/><span class="n">input</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"paypaı.com"</span><span class="p">;</span><span class="w"/><span class="c1">// contains the homoglyph "ı" (small i without a dot)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">containsSuspiciousCharacters</span><span class="p">(</span><span class="n">input</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Suspicious characters found: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">input</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span></span></span></code></pre></div></div><p>This example shows how a simple check can help identify potentially dangerous signs and act accordingly.</p><p><strong>Allowing specific character sets</strong> : Another risk mitigation measure is limiting the use of certain characters. For example, an application might specify that only Latin characters are allowed in usernames or URL paths to reduce the risk of homoglyph attacks. This restriction helps reduce the attack surface.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">CharacterWhitelistExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isValidInput</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">input</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">return</span><span class="w"/><span class="n">input</span><span class="p">.</span><span class="na">matches</span><span class="p">(</span><span class="s">"^[a-zA-Z0-9]*$"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"username"</span><span class="p">;</span><span class="w"/><span class="c1">// contains a non-Latin character</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isValidInput</span><span class="p">(</span><span class="n">username</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Username is valid."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Username contains invalid characters."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span></span></span></code></pre></div></div><p>This example shows a simple method for restricting the allowed characters in input to prevent using homoglyphs.</p><h3 id="technical-challenges-in-homoglyph-recognition">Technical challenges in homoglyph recognition</h3><p>Recognising homoglyphs represents a significant technical challenge. One reason for this is the number of characters in the Unicode standard. Unicode includes thousands of characters from different writing systems that may look similar or identical. An algorithm intended to identify such characters must be able to distinguish between visual similarities and actual meaning.</p><p>Another problem is that not all applications or systems can display Unicode correctly. In some cases, different characters may appear identical through the rendering process, making it even more difficult for you to distinguish between legitimate and fake content. These technical challenges require the development of robust checking mechanisms that ensure that such attempts at deception can be detected.</p><h3 id="best-practices-for-developers">Best practices for developers</h3><p>As a developer, you play a crucial role in preventing CWE-1007. Here are some best practices to consider when developing secure applications:</p><p><strong>Input validation</strong> : User input data should always be validated to ensure that no dangerous characters are used. If possible, only certain character sets should be allowed.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">InputValidationExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isValidInput</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">input</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">return</span><span class="w"/><span class="n">input</span><span class="p">.</span><span class="na">matches</span><span class="p">(</span><span class="s">"^[a-zA-Z0-9]*$"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="n">String</span><span class="w"/><span class="n">userInput</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"hello123"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isValidInput</span><span class="p">(</span><span class="n">userInput</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Input is valid."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Input contains invalid characters."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span></span></span></code></pre></div></div><p><strong>Escape and encode</strong> : Data used for display or transmission should always be escaped and encoded to ensure no harmful characters are inserted unnoticed.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import org.apache.commons.text.StringEscapeUtils;</span></span><span class="line"><span class="cl">   public class EscapeEncodeExample {</span></span><span class="line"><span class="cl">       public static void main(String[] args) {</span></span><span class="line"><span class="cl">           String userInput = "<span class="nt">&lt;script&gt;</span>alert('XSS');<span class="nt">&lt;/script&gt;</span>";</span></span><span class="line"><span class="cl">           String escapedInput = StringEscapeUtils.escapeHtml4(userInput);</span></span><span class="line"><span class="cl">           System.out.println("Escaped Input: " + escapedInput);</span></span><span class="line"><span class="cl">       }</span></span><span class="line"><span class="cl">   }</span></span></code></pre></div></div><p>This example shows how potentially malicious input can be correctly handled to prevent attacks such as Cross-Site Scripting (XSS).</p><p><strong>Conscious use of fonts</strong> : Choosing the right font can help you recognise homoglyphs better. Some fonts distinguish more clearly between similar-looking characters, making it easier for you to spot differences. Examples of such fonts include &lsquo;Consolas&rsquo;, &lsquo;Courier New&rsquo;, and &lsquo;DejaVu Sans Mono&rsquo;. These fonts are handy when characters need to be clearly distinguished from each other, for example, in source code or security-related information.</p><p><strong>Provide additional contextual information</strong> : If you&rsquo;re asked to review sensitive information like URLs or usernames, it can be helpful to provide additional contextual information to help verify legitimacy. This includes warnings or visual markers that alert you that certain signs may be potentially dangerous.</p><h3 id="case-study-homoglyph-domain-attack">Case Study: Homoglyph Domain Attack</h3><p>A famous case study that illustrates the danger of homoglyphs is the attack via a fake “apple.com” domain. In this case, the attacker registered a domain that consisted of Cyrillic characters that visually looked identical to &ldquo;apple.com.&rdquo; When you clicked on this domain, you were taken to a website that looked almost exactly like the genuine Apple website. This allowed the attacker to steal sensitive information, such as login credentials.</p><p>Such attacks highlight the importance of paying attention to the visual distinction of characters when presenting information. Without careful examination, you would have no technical way of recognising the fake domain. Such attacks highlight the need to use technical and human defences to protect yourself from deception.</p><h3 id="homoglyph-recognition-tools">Homoglyph recognition tools</h3><p>There are some tools and libraries designed explicitly for homoglyph recognition. These tools can help you, as a developer and security professional identify and remediate potential vulnerabilities in your systems. The most well-known tools include:</p><p><strong>DNSTwist</strong> : A tool used to generate similar domain names and check whether they have been registered for phishing attacks. DNSTwist can help detect potential threats early and take countermeasures.</p><p><strong>Homoglyph Detector Libraries</strong> : Various program libraries recognise homoglyphs in strings. These libraries can be integrated into applications to ensure suspicious characters are detected. Such libraries are particularly useful when users provide security-relevant input.</p><p><strong>Unicode-Analyse-Tools</strong> : These tools can help check characters for their Unicode representation, ensuring that similar characters are not used unnoticed. Unicode analysis tools can help you identify and fix problems early.</p><h3 id="which-cwes-are-often-used-in-conjunction-with-cwe-1007">Which CWEs are often used in conjunction with CWE-1007?</h3><p>CWE-1007 is often used with other vulnerabilities that undermine the security and trustworthiness of applications. Here are some of the most common CWEs that frequently appear associated with CWE-1007:</p><h4 id="cwe-601-open-redirect">CWE-601: Open Redirect:</h4><p>This vulnerability occurs when an application allows users to be redirected to a different, potentially dangerous URL without sufficient validation. CWE-1007 can be used here to deceive users by presenting a seemingly legitimate but homoglyph-manipulated URL redirecting to a malicious resource.</p><h4 id="cwe-643-improper-neutralization-of-data-within-xpath-expressions">CWE-643: Improper Neutralization of Data within XPath Expressions:</h4><p>When user input is used in XPath queries without sufficient validation, CWE-1007 can allow attackers to use visually similar characters to manipulate the query and access data that would not usually be available.</p><h4 id="cwe-79-cross-site-scripting-xss">CWE-79: Cross-Site Scripting (XSS):</h4><p>Cross-site scripting occurs when an application does not adequately escape or validate user input returned for display. CWE-1007 can also be dangerous here if homoglyphs are used to hide malicious scripts in a context that looks harmless to the user.</p><h4 id="cwe-20-improper-input-validation">CWE-20: Improper Input Validation:</h4><p>This general vulnerability occurs when an application does not sufficiently validate user input. CWE-1007 is particularly problematic if no normalisation and validation measures are taken, as visually similar characters could pass as legitimate input.</p><h4 id="cwe-77-command-injection">CWE-77: Command Injection:</h4><p>Command injection occurs when a user&rsquo;s input is used in a way that results in the execution of system commands. By using homoglyphs, attackers could manipulate input so that the actual command deviates from the expected execution, which could give an attacker control over the system.</p><p>Combining CWE-1007 with these other vulnerabilities allows attackers to carry out significantly more complex and effective attacks. They leverage the visual deception of homoglyphs to exploit vulnerabilities that might be easier to detect without such deception.</p><h3 id="example-application-in-vaadin-flow">Example application in Vaadin Flow</h3><p>To illustrate all the best practices discussed in a real application, here is an example application in Vaadin Flow. Vaadin Flow is a popular open-source Java framework for building modern web applications. This sample application shows how you can implement Unicode normalisation, input validation, escaping, and encoding methods in a secure web application to prevent homoglyph-based attacks.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.button.Button</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.notification.Notification</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.orderedlayout.VerticalLayout</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.textfield.TextField</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.router.Route</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.apache.commons.text.StringEscapeUtils</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.text.Normalizer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="s">"homoglyph-protection"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">HomoglyphProtectionView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">HomoglyphProtectionView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Username input field</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">TextField</span><span class="w"/><span class="n">userInputField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">(</span><span class="s">"Enter username"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">userInputField</span><span class="p">.</span><span class="na">setHelperText</span><span class="p">(</span><span class="s">"Only Latin letters and numbers allowed."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Validation button</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Button</span><span class="w"/><span class="n">validateButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Validate"</span><span class="p">,</span><span class="w"/><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"/><span class="n">userInput</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">userInputField</span><span class="p">.</span><span class="na">getValue</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Step 1: Unicode normalization</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"/><span class="n">normalizedInput</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Normalizer</span><span class="p">.</span><span class="na">normalize</span><span class="p">(</span><span class="n">userInput</span><span class="p">,</span><span class="w"/><span class="n">Normalizer</span><span class="p">.</span><span class="na">Form</span><span class="p">.</span><span class="na">NFKC</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Step 2: Input validation - only Latin letters and numbers</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">isValidInput</span><span class="p">(</span><span class="n">normalizedInput</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Username contains invalid characters."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Step 3: Escape potentially harmful input</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">String</span><span class="w"/><span class="n">escapedInput</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">StringEscapeUtils</span><span class="p">.</span><span class="na">escapeHtml4</span><span class="p">(</span><span class="n">normalizedInput</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Notification if input is valid</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Input is valid: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">escapedInput</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">add</span><span class="p">(</span><span class="n">userInputField</span><span class="p">,</span><span class="w"/><span class="n">validateButton</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Input validation method that only allows Latin letters and numbers</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">invalid</span><span class="w"/><span class="nf">input</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">input</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="n">input</span><span class="p">.</span><span class="na">matches</span><span class="p">(</span><span class="s">"^[a-zA-Z0-9]*$"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this Vaadin Flow application, what you have learned before is put into practice:</p><p><strong>Unicode normalisation</strong> : User input is normalised to ensure that visually similar characters that have different Unicode values ​​are treated correctly.</p><p><strong>Input validation</strong> : The input is checked to allow only Latin letters and numbers. This prevents the use of homoglyphs from other character sets.</p><p><strong>Escape and encode</strong> : The input is escaped before further processing to remove potentially harmful characters and prevent attacks such as Cross-Site Scripting (XSS).</p><p>This sample application shows how to apply the secure user input best practices discussed previously in a real-world web application to minimise homoglyph risks.</p><h3 id="conclusion">Conclusion</h3><p>CWE-1007, insufficient visual discrimination of homoglyphs, is a vulnerability that is becoming increasingly important in today&rsquo;s digital world. Attackers use the visual similarity of characters to deceive you, steal your data, or compromise systems. The threat of homoglyph attacks can have serious consequences, especially if you and your systems are not adequately trained and prepared.</p><p>Preventing such attacks requires technical solutions, user training, and developer best practices. By implementing appropriate defence strategies, you can minimise the risk of homoglyph attacks and ensure the security of your systems and data. Everyone must be involved—from developers to end users—be aware of the risks and take steps to mitigate them.</p><h3 id="next-steps">Next Steps</h3><p>To better understand and protect yourself from CWE-1007, you should:</p><ul><li>Train your employees about the danger of homoglyphs and teach them to recognize suspicious characters.</li><li>Enable security features in your browsers and applications to warn users about fake domains.</li><li>Integrate homoglyph recognition tools and libraries into your software development processes.</li><li>Conduct regular security scans and code reviews to ensure no malicious characters have been inserted into your systems.</li></ul><p>Security is an ongoing process, and the homoglyph threat requires continued vigilance and adaptation. Stay informed, train your team, and use the right tools to ensure homoglyphs aren&rsquo;t a gap in your security strategy. Together, we can help improve the security of our digital world and successfully ward off homoglyph threats.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Uncategorized</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2024/10/601DF759-C507-4579-8EB3-C3DB6498B2C3.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/10/601DF759-C507-4579-8EB3-C3DB6498B2C3.jpg"/><enclosure url="https://svenruppert.com/images/2024/10/601DF759-C507-4579-8EB3-C3DB6498B2C3.jpg" type="image/jpeg" length="0"/></item><item><title>The History of Parallel Processing in Java: From Threads to Virtual Threads</title><link>https://svenruppert.com/posts/the-history-of-parallel-processing-in-java-from-threads-to-virtual-threads/</link><pubDate>Fri, 01 Nov 2024 22:02:37 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/the-history-of-parallel-processing-in-java-from-threads-to-virtual-threads/</guid><description>Since the early days of computer science, parallel processing has represented one of the greatest challenges and opportunities. Since its inception in 1995, Java has undergone a significant journey in the world of parallel programming to provide developers with ever-better tools. This story is a fascinating journey through threads, the executor framework, fork/join, parallel streams, CompletableFuture and the latest developments in Project Loom. In this blog post, we take a detailed look at the evolution of parallel processing in Java and the innovations that came with it.</description><content:encoded>&lt;![CDATA[<p>Since the early days of computer science, parallel processing has represented one of the greatest challenges and opportunities. Since its inception in 1995, Java has undergone a significant journey in the world of parallel programming to provide developers with ever-better tools. This story is a fascinating journey through threads, the executor framework, fork/join, parallel streams, CompletableFuture and the latest developments in Project Loom. In this blog post, we take a detailed look at the evolution of parallel processing in Java and the innovations that came with it.</p><ol><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#locking-mechanisms-in-thread-programming-in-java">Locking mechanisms in thread programming in Java</a><ol><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#synchronized-keyword"><code>synchronized</code> keyword</a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#reentrantlock"><code>ReentrantLock</code></a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#readwritelock"><code>ReadWriteLock</code></a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#stampedlock"><code>StampedLock</code></a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#semaphore"><code>Semaphore</code></a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#summary-of-the-locking-mechanisms">Summary of the locking mechanisms</a></li></ol></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#the-early-years-threads-and-runnable">The early years: Threads and<code>Runnable</code></a><ol><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#using-threads-advantages-and-disadvantages">Using threads: advantages and disadvantages</a></li></ol></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#java-5-the-executor-framework-and-java-util-concurrent">Java 5: The Executor Framework and<code>java.util.concurrent</code></a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#java-7-fork-join-framework">Java 7: Fork/Join Framework</a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#java-8-parallel-streams">Java 8: Parallel Streams</a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#java-8-completablefuture">Java 8: CompletableFuture</a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#java-9-to-java-19-improvements-and-project-loom">Java 9 to Java 19: Improvements and Project Loom</a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#the-evolution-of-parallel-processing-in-java-conclusion">The Evolution of Parallel Processing in Java: Conclusion</a></li><li><a href="https://svenruppert.com/2024/11/01/auto-draft/#looking-into-the-future">Looking into the future</a></li></ol><h3 id="locking-mechanisms-in-thread-programming-in-java">Locking mechanisms in thread programming in Java</h3><p>To make parallel processing safe and efficient, Java provides various locking mechanisms that help coordinate access to shared resources and avoid data corruption. These mechanisms are critical to ensure data consistency and integrity, especially when multiple threads access the same resources simultaneously. The most critical locking mechanisms in Java are described below:</p><h4 id="synchronized-keyword"><code>synchronized</code> keyword</h4><p>The<code>synchronized</code> keyword is the most basic mechanism for synchronising threads in Java. It ensures that only one thread can access a synchronised method or code block at a time. This mechanism uses a so-called monitor, which is automatically locked and unlocked.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">synchronized</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">increment</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Critical section</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">counter</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Advantages:</strong></p><p>- Easy to use and integrate into code.</p><p>- Automatic management of bans.</p><p><strong>Disadvantages:</strong></p><p>- Can lead to performance issues if many threads want to access the synchronised resource simultaneously.</p><p>- No flexibility, such as B. the ability to set timeouts or conditional locks.</p><h4 id="reentrantlock"><code>ReentrantLock</code></h4><p><code>ReentrantLock</code> is a class from the<code>java.util.concurrent.locks</code> package that provides greater flexibility than the<code>synchronized</code> keyword. As the name suggests,<code>ReentrantLock</code> is a &ldquo;reentrant&rdquo; lock, meaning that a thread that already holds the lock can reacquire it without getting into a deadlock.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">ReentrantLock</span><span class="w"/><span class="n">lock</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ReentrantLock</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">increment</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">lock</span><span class="p">.</span><span class="na">lock</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Critical section</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">counter</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">lock</span><span class="p">.</span><span class="na">unlock</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Advantages:</strong></p><p>- Provides more control over the locking process, e.g. B. the ability to acquire locks with timeouts (<code>tryLock</code>).</p><p>- Supports fair locking, which ensures that threads are granted access in the requested order (<code>lock(true)</code>).</p><p><strong>Disadvantages:</strong></p><p>- Requires manual release of locks, increasing the risk of deadlocks if<code>unlock</code> is not performed correctly.</p><h4 id="readwritelock"><code>ReadWriteLock</code></h4><p><code>ReadWriteLock</code> is a unique locking mechanism that distinguishes read and write locks. It consists of two locks: a read lock and a write lock. Multiple threads can acquire a read lock simultaneously as long as no write operation is performed. However, only one thread can acquire the write lock, keeping writes exclusive.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">ReentrantReadWriteLock</span><span class="w"/><span class="n">lock</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ReentrantReadWriteLock</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">read</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">lock</span><span class="p">.</span><span class="na">readLock</span><span class="p">().</span><span class="na">lock</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Read operation</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">lock</span><span class="p">.</span><span class="na">readLock</span><span class="p">().</span><span class="na">unlock</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">write</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">lock</span><span class="p">.</span><span class="na">writeLock</span><span class="p">().</span><span class="na">lock</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Write operation</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">lock</span><span class="p">.</span><span class="na">writeLock</span><span class="p">().</span><span class="na">unlock</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Advantages:</strong></p><p>- Increases concurrency as multiple threads can read simultaneously as long as there are no writes.</p><p>- Reduces the likelihood of blocking read access.</p><p><strong>Disadvantages:</strong></p><p>- More complex to use than<code>synchronized</code> or<code>ReentrantLock</code>.</p><p>- Requires careful design to ensure no deadlocks or race conditions occur.</p><h4 id="stampedlock"><code>StampedLock</code></h4><p><code>StampedLock</code> is another variant of<code>ReadWriteLock</code> introduced in Java 8. Unlike<code>ReadWriteLock</code>,<code>StampedLock</code> provides optimistic read locks that enable even higher concurrency. A stamp is returned each time the lock is acquired to ensure the data remains valid.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">StampedLock</span><span class="w"/><span class="n">lock</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StampedLock</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">read</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">long</span><span class="w"/><span class="n">stamp</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">lock</span><span class="p">.</span><span class="na">tryOptimisticRead</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Read operation</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">lock</span><span class="p">.</span><span class="na">validate</span><span class="p">(</span><span class="n">stamp</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">stamp</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">lock</span><span class="p">.</span><span class="na">readLock</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Read again</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">lock</span><span class="p">.</span><span class="na">unlockRead</span><span class="p">(</span><span class="n">stamp</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Advantages:</strong></p><p>- Provides optimistic read locks that enable high concurrency as long as no writes occur.</p><p>- Lower read operation overhead compared to traditional locks.</p><p><strong>Disadvantages:</strong></p><p>- Complex to use as developers need to ensure that the stamp is valid.</p><p>- No “re-enterable” locks, which may limit usage for some scenarios.</p><h4 id="semaphore"><code>Semaphore</code></h4><p><code>Semaphore</code> is another synchronisation mechanism that allows several threads to access a resource simultaneously. It is often used to control simultaneous access to limited resources.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Semaphore</span><span class="w"/><span class="n">semaphore</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Semaphore</span><span class="p">(</span><span class="n">3</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">accessResource</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">semaphore</span><span class="p">.</span><span class="na">acquire</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Access the resource</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">InterruptedException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">interrupt</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">semaphore</span><span class="p">.</span><span class="na">release</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Advantages:</strong></p><p>- Allows you to limit the number of threads that are allowed to access a resource at the same time.</p><p>- Flexible and applicable to various scenarios, e.g. B. to implement pooling mechanisms.</p><p><strong>Disadvantages:</strong></p><p>- Can become complex when using multiple semaphores in an application.</p><p>- Requires careful management to ensure that<code>acquire</code> and<code>release</code> are called correctly.</p><h4 id="summary-of-the-locking-mechanisms">Summary of the locking mechanisms</h4><p>Java offers a variety of locking mechanisms, each suitable for different scenarios. The<code>synchronized</code> keyword is easy to use, but less flexible for complex scenarios.<code>ReentrantLock</code> and<code>ReadWriteLock</code> provide more control and enable higher parallelism, while<code>StampedLock</code> is suitable for particularly demanding read operations.<code>Semaphore</code>, on the other hand, is ideal for controlling concurrent access to limited resources. Choosing the proper mechanism depends on the application&rsquo;s requirements, particularly regarding concurrency, resource contention, and maintainability.</p><h3 id="the-early-years-threads-and-runnable">The early years: Threads and<code>Runnable</code></h3><p>Initially, Java&rsquo;s concept of threads was the cornerstone of parallel processing. Java was developed as a platform-independent language intended for networked, distributed systems. With a focus on multithreading, Java offered a built-in<code>Thread</code> class and the<code>Runnable</code> interface, making it possible to execute multiple tasks simultaneously.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">MyTask</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Runnable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">run</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Task running in parallel</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Hello from thread: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Main</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Thread</span><span class="w"/><span class="n">thread</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Thread</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">MyTask</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">thread</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In the early versions of Java, threads were the only option for parallel processing. These were supported by operating system threads, which meant managing threads could be heavy and resource-intensive. Developers had to take care of details such as synchronizing shared resources and avoiding deadlocks themselves, which made developing parallel applications challenging.</p><h4 id="using-threads-advantages-and-disadvantages">Using threads: advantages and disadvantages</h4><p><strong>Advantages of threads</strong></p><p><strong>Simple modelling of parallel tasks</strong> : Threads allow developers to divide parallel tasks into separate units that can run concurrently.</p><p><strong>Direct control</strong> : Threads give developers fine control over parallel execution, which is particularly useful when specific thread management requirements exist.</p><p><strong>Good support from the operating system</strong> : Threads are supported directly by the operating system, meaning they can access all system resources and benefit from operating system optimisations.</p><p><strong>Disadvantages of threads</strong></p><p><strong>Complexity of synchronisation</strong> : When multiple threads access shared resources, developers must use synchronisation mechanisms such as<code>synchronized</code> blocks or<code>locks</code> to avoid data corruption. Such mechanisms ensure that only one thread can access a critical resource at a time, ensuring data consistency. However, this often results in complicated and error-prone code, as developers must carefully ensure that all necessary sections are correctly synchronised. An incorrectly set or forgotten synchronisation block can lead to severe errors, such as race conditions, in which the program&rsquo;s output depends on the timing of thread executions. Additionally, synchronisation mechanisms such as<code>locks</code> or<code>synchronized</code> blocks can lead to a performance penalty as threads often have to wait until a resource is released, which limits parallelism. These wait times can cause bottlenecks in more complex applications, mainly when multiple threads compete for different resources. Therefore, correctly applying synchronisation techniques requires a deep understanding of thread interactions and careful design to ensure data integrity and maximise performance.</p><p><strong>Resource intensive</strong> : Each thread requires space for its stack and additional resources such as thread handling and context switching. These resource requirements can quickly add up with many threads and lead to system overload. In particular, the memory consumption for the thread stacks and the management of the threads by the operating system lead to increased resource requirements. With a large number of threads, the frequency of context switches also increases, which can lead to a significant reduction in overall performance. This often makes threads inefficient and difficult to manage at a large scale.</p><p><strong>The danger of deadlocks</strong> : When using synchronisation mechanisms, there is a risk of deadlocks when two or more threads are blocked in a cyclic dependency on each other. Deadlocks often arise when multiple threads hold different locks in a mismatched order, waiting for other threads to release the needed resources. This leads to a situation where none of the threads can continue working because they are all waiting for the others to release resources. Deadlocks are often difficult to reproduce and debug because they only occur under certain runtime conditions. Strategies to avoid deadlocks include using timeout mechanisms, avoiding nested locks, and implementing lock ordering schemes to ensure all threads acquire locks in the same order.</p><p><strong>Difficult scalability</strong> : Manual management of threads makes it difficult to scale applications, especially on systems with many processor cores. One of the main reasons for this is the challenge of determining the optimal number of threads to use system resources efficiently. Too many threads can cause system overload because management and context switching between threads consume significant CPU resources. On the other hand, too few threads can result in under-utilization of available processor resources, degrading overall performance. Additionally, it is challenging to adapt thread management to the dynamic needs of an application, especially when the load is variable. Developers often have to resort to complex heuristics or dynamic thread pools to control the number of active threads, which significantly complicates the implementation and maintenance of the application. These challenges make it complicated to efficiently scale applications to modern multicore processors because the balance between parallelism and overhead is challenging.</p><h3 id="java-5-the-executor-framework-and-javautilconcurrent">Java 5: The Executor Framework and<code>java.util.concurrent</code></h3><p>The introduction of Java 5 in 2004 introduced the<code>java.util.concurrent</code> package, intended to address many of the problems of early parallel programming in Java. The Executor framework enabled higher abstraction of threads. Instead of starting and managing threads manually, developers could now rely on a task-based architecture to pass tasks to an<code>ExecutorService</code>.</p><p>The Executor framework introduced classes like<code>ThreadPoolExecutor</code>,<code>ScheduledExecutorService</code>, and many synchronization helper classes like<code>Semaphore</code>,<code>CountDownLatch</code>, and<code>ConcurrentHashMap</code>. This not only made thread management easier but also led to more efficient use of system resources.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">ExecutorService</span><span class="w"/><span class="n">executor</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Executors</span><span class="p">.</span><span class="na">newFixedThreadPool</span><span class="p">(</span><span class="n">4</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">executor</span><span class="p">.</span><span class="na">submit</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Task is running in thread: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">Thread</span><span class="p">.</span><span class="na">currentThread</span><span class="p">().</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">executor</span><span class="p">.</span><span class="na">shutdown</span><span class="p">();</span></span></span></code></pre></div></div><p>The Executor Framework changed the way developers modelled parallel tasks. Instead of focusing on thread creation, they could define tasks and let the infrastructure handle execution.</p><h3 id="java-7-forkjoin-framework">Java 7: Fork/Join Framework</h3><p>Java 7 introduced the Fork/Join framework in 2011, explicitly designed for computationally intensive tasks that could be broken down into smaller subtasks. The fork/join framework provided a powerful recursion and divide-and-conquer infrastructure, allowing complex problems to be broken down into smaller, more manageable sub-problems. This division enabled the efficient use of modern multi-core processors.</p><p>The fork/join framework was particularly useful for problems broken down into independent subproblems, such as B. calculating Fibonacci numbers, sorting large arrays (e.g. with merge sort) or processing large amounts of data in parallel. The central component of the framework is the<code>ForkJoinPool</code>, which handles the management of task transfer between threads. The<code>ForkJoinPool</code> uses the so-called work stealing process, in which less busy threads take over work from more busy threads. This ensures better load balancing and increases the efficiency of parallel processing.</p><p>Another advantage of the fork/join framework is the ability to handle recursive tasks efficiently. Developers can use classes like<code>RecursiveTask</code> or<code>RecursiveAction</code> to define tasks that either provide a return value (<code>RecursiveTask</code>) or do not require a return (<code>RecursiveAction</code>). The fork/join approach makes it possible to recursively split the tasks (<code>fork</code>) and then combine the results again (<code>join</code>).</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">ForkJoinPool forkJoinPool = new ForkJoinPool();</span></span><span class="line"><span class="cl">forkJoinPool.invoke(new RecursiveTask<span class="nt">&lt;Integer&gt;</span>() {</span></span><span class="line"><span class="cl">    @Override</span></span><span class="line"><span class="cl">    protected Integer compute() {</span></span><span class="line"><span class="cl">        // Split and calculate task</span></span><span class="line"><span class="cl">        if (/* base case reached */) {</span></span><span class="line"><span class="cl">            return 42;  // Example return value</span></span><span class="line"><span class="cl">        } else {</span></span><span class="line"><span class="cl">            // Divide the task further</span></span><span class="line"><span class="cl">            RecursiveTask<span class="nt">&lt;Integer&gt;</span> subtask1 = new Subtask();</span></span><span class="line"><span class="cl">            RecursiveTask<span class="nt">&lt;Integer&gt;</span> subtask2 = new Subtask();</span></span><span class="line"><span class="cl">            subtask1.fork();</span></span><span class="line"><span class="cl">            subtask2.fork();</span></span><span class="line"><span class="cl">            return subtask1.join() + subtask2.join();</span></span><span class="line"><span class="cl">        }</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">});</span></span></code></pre></div></div><p>The fork/join framework delivered significant performance improvements for CPU-intensive workloads, particularly for tasks easily broken down into smaller, independent pieces. It made it easier to write parallel algorithms without the developer worrying about thread distribution details. The &lsquo;ForkJoinPool&rsquo; handles the distribution of tasks and uses work stealing to ensure that the processor resources are used optimally. This significantly increases performance compared to manual thread management, especially for computationally intensive and highly parallelisable tasks.</p><h3 id="java-8-parallel-streams">Java 8: Parallel Streams</h3><p>Java 8, released in 2014, marked a milestone in the evolution of Java, particularly with the introduction of lambda expressions, streams, and functional programming. These new features made the language more flexible and easier to use, especially for parallel operations. One of the most significant new features for parallel processing was Parallel Streams.</p><p>Parallel Streams allowed developers to effortlessly parallelise operations across collections without explicitly dealing with threads or synchronisation. This was achieved by integrating the fork/join framework behind the scenes. Parallel streams use the<code>ForkJoinPool</code> internally to distribute tasks efficiently across multiple processor cores. This approach is based on the divide-and-conquer design principle, in which an enormous task is broken down into smaller subtasks that can be executed in parallel.</p><p>The developer uses the<code>parallelStream()</code> method to convert a collection into a parallel stream. This results in the processing of the collection&rsquo;s elements occurring simultaneously, with individual parts of the task being automatically distributed across the available CPU cores. In contrast to manual thread management, this approach offers a high level of abstraction and relieves the developer of the complex management of threads and synchronisation.</p><p>An example of using Parallel Streams is processing a list of numbers in parallel. Here, the operations applied to each element are carried out simultaneously, which can achieve a significant increase in performance on large data sets:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">List<span class="nt">&lt;Integer&gt;</span> numbers = Arrays.asList(1, 2, 3, 4, 5);</span></span><span class="line"><span class="cl">numbers.parallelStream().map(n -&gt; n * n).forEach(System.out::println);</span></span></code></pre></div></div><p>The design of Parallel Streams aims to simplify the development of parallel applications by using a declarative syntax that allows the developer to focus on the logic of data processing rather than on the details of thread management and distribution of the tasks to deal with. This higher abstraction makes parallel processing more accessible, which leads to better performance, especially in multi-core systems.</p><h3 id="java-8-completablefuture">Java 8: CompletableFuture</h3><p>The<code>CompletableFuture</code> API was also introduced in Java 8 and significantly expanded the possibilities of asynchronous programming.<code>CompletableFuture</code> allows the creation, chaining and combining asynchronous tasks, making it a handy tool for developing event-driven applications. Using methods like<code>thenApply</code>,<code>thenAccept</code> and<code>thenCombine</code> makes it easy to define a sequence of asynchronous operations that should be executed sequentially or whose results can be combined.</p><p>A<code>CompletableFuture</code> represents a future calculation and allows the different steps of the workflow to be defined declaratively. For example, an asynchronous calculation can be started, and the result can be processed further without worrying about explicitly managing the threads. This significantly simplifies the code and makes it more readable.</p><p>An example of using<code>CompletableFuture</code> shows how to execute multiple asynchronous operations one after the other:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">CompletableFuture</span><span class="p">.</span><span class="na">supplyAsync</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="s">"Hello"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">thenApply</span><span class="p">(</span><span class="n">s</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">s</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" World"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="p">.</span><span class="na">thenAccept</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">::</span><span class="n">println</span><span class="p">);</span></span></span></code></pre></div></div><p>In this example, an asynchronous calculation is first started that returns the string &ldquo;Hello&rdquo;. Then, the result of this calculation is modified by the<code>thenApply</code> method by adding " World". Finally,<code>thenAccept</code> prints the result on the console. The entire process occurs asynchronously, without the developer worrying about explicitly managing threads.</p><p>The architecture of<code>CompletableFuture</code> is based on the concept of so-called “completion stages”, which allow the creation of asynchronous pipelines. Each<code>Completion Stage</code> can either trigger a new calculation or process the result of a previous calculation. This enables the modelling of complex workflows, such as: E.g. executing multiple tasks in parallel and then combining the results (<code>thenCombine</code>), or defining actions that should be carried out in the event of an error (<code>exceptionally</code>).</p><p>Another significant advantage of<code>CompletableFuture</code> is the ability to combine asynchronous tasks. For example, two independent asynchronous calculations can be performed in parallel, and their results can then be merged:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">CompletableFuture<span class="nt">&lt;Integer&gt;</span> future1 = CompletableFuture.supplyAsync(() -&gt; 10);</span></span><span class="line"><span class="cl">CompletableFuture<span class="nt">&lt;Integer&gt;</span> future2 = CompletableFuture.supplyAsync(() -&gt; 20);</span></span><span class="line"><span class="cl">CompletableFuture<span class="nt">&lt;Integer&gt;</span> combinedFuture = future1.thenCombine(future2, (result1, result2) -&gt; result1 + result2);</span></span><span class="line"><span class="cl">combinedFuture.thenAccept(result -&gt; System.out.println("Combined Result: " + result));</span></span></code></pre></div></div><p>In this example, two calculations are performed in parallel, and their results are combined. The<code>thenCombine</code> method allows the results of the two futures to be added, and<code>thenAccept</code> prints the combined result.</p><p>This model allows complex workflows to be created without explicitly relying on threads or callbacks, making the code cleaner, more modular, and easier to maintain.<code>CompletableFuture</code> also provides methods such as<code>allOf()</code> and<code>anyOf()</code> that allow multiple futures to be monitored and processed simultaneously. This is particularly useful for scenarios where numerous independent tasks must be executed in parallel.</p><p>Overall, the<code>CompletableFuture</code> API makes asynchronous programming in Java much more accessible and allows developers to develop reactive and non-blocking applications with relatively little effort.</p><h3 id="java-9-to-java-19-improvements-and-project-loom">Java 9 to Java 19: Improvements and Project Loom</h3><p>After Java 8, the parallel programming models continuously improved. Java 9 brought improvements to the fork/join framework and introduced the &lsquo;Flow&rsquo; API, which supported a reactive streaming model. Java 9 to 17 focused primarily on performance improvements, security, and the modularization of the JDK (Project Jigsaw).</p><p>However, one of the most significant innovations in recent times is Project Loom. Since Java 19,<code>Virtual Threads</code> has been available as a preview feature to revolutionise parallel programming in Java. Virtual threads are lightweight threads that enable many concurrent tasks to run without the typical overhead of traditional operating system threads. While traditional threads are limited by resource costs (such as memory for stacks and context switches), virtual threads are designed to be much more resource-efficient. This means developers can create millions of virtual threads that work independently without overloading the system.</p><p>Virtual threads are handy for server-side applications that handle many concurrent connections, such as web servers or microservices. In traditional approaches, handling each request in its thread often leads to scaling problems because hardware resources limit the number of possible threads. Virtual threads, on the other hand, allow each incoming request to be handled in its virtual thread, significantly increasing parallelism.</p><p>Virtual threads work because they are efficiently managed by the Java runtime system rather than directly by the operating system like traditional threads. This significantly speeds up context switching and makes managing millions of threads realistic. Virtual threads are programmed like traditional threads, allowing existing code to be easily adapted to take advantage of this new technology.</p><p>A simple example of using virtual threads shows how to use an<code>executor</code> to execute a task in a virtual thread:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">try</span><span class="w"/><span class="p">(</span><span class="kd">var</span><span class="w"/><span class="n">executor</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Executors</span><span class="p">.</span><span class="na">newVirtualThreadPerTaskExecutor</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">executor</span><span class="p">.</span><span class="na">submit</span><span class="p">(()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Running in a virtual thread"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This example creates an executor that uses a new virtual thread for each task. The<code>submit</code> method starts the task in a virtual thread, which requires significantly fewer resources than a traditional thread.</p><p>Project Loom can potentially make parallel programming in Java much more accessible by eliminating the need for developers to worry about thread scaling. Virtual threads are significantly more efficient and offer much higher parallelism without the programmer having to work with thread pools or complex synchronisation mechanisms explicitly. This increased scalability is particularly valuable in applications where concurrent operations must be dynamically scaled, as in many modern web applications and cloud environments. The introduction of virtual threads makes Java an even stronger choice for developing highly scalable, parallel applications by dramatically reducing the complexity of thread management.</p><h3 id="the-evolution-of-parallel-processing-in-java-conclusion">The Evolution of Parallel Processing in Java: Conclusion</h3><p>The journey of parallel processing in Java reflects the language&rsquo;s evolutionary nature. From the early days of threads, when developers had to rely on low-level APIs, to the highly abstract paradigms such as the Executor framework, Fork/Join, and Parallel Streams, Java has continually introduced improvements to make parallel application development easier.</p><p>With recent developments such as &lsquo;CompletableFuture&rsquo; and Project Loom, Java can meet the needs of modern software development, especially in scalability and performance. Parallel processing in Java is now simpler, safer and more efficient than ever before - providing developers with powerful tools to exploit the full potential of modern multicore systems.</p><h3 id="looking-into-the-future">Looking into the future</h3><p>With Project Loom on the path to stability, we could see a further shift in focus towards even simpler and more performant parallel processing techniques. Virtual threads will likely pave the way for new frameworks and libraries that benefit from this lightweight parallelism. Developers will continue to have access to the best parallel processing tools without worrying about thread management&rsquo;s complexities.</p><p>Java has proven that it can keep changing with the times as a parallel programming language—a language that rises to the challenges and adapts to the needs of modern developers. Java&rsquo;s history of parallel processing is one of progress and continuous innovation. With the upcoming developments, it will remain exciting to see what new possibilities the future will bring us.</p>
]]></content:encoded><category>Concurrency</category><category>Java</category><media:content url="https://svenruppert.com/images/2024/11/B9112628-C012-4D64-A3EE-C7E4565493DB.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/11/B9112628-C012-4D64-A3EE-C7E4565493DB.jpg"/><enclosure url="https://svenruppert.com/images/2024/11/B9112628-C012-4D64-A3EE-C7E4565493DB.jpg" type="image/jpeg" length="0"/></item><item><title>CWE-778: Lack of control over error reporting in Java</title><link>https://svenruppert.com/posts/cwe-778-lack-of-control-over-error-reporting-in-java/</link><pubDate>Fri, 18 Oct 2024 14:07:20 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/cwe-778-lack-of-control-over-error-reporting-in-java/</guid><description>Learn how inadequate control over error reporting leads to security vulnerabilities and how to prevent them in Java applications. Safely handling error reports is a central aspect of software development, especially in safety-critical applications. CWE-778 describes a vulnerability caused by inadequate control over error reports. This post will analyse the risks associated with CWE-778 and show how developers can implement safe error-handling practices to avoid such vulnerabilities in Java programs.</description><content:encoded>&lt;![CDATA[<h2 id="learn-how-inadequate-control-over-error-reporting-leads-to-security-vulnerabilities-and-how-to-prevent-them-in-java-applications">Learn how inadequate control over error reporting leads to security vulnerabilities and how to prevent them in Java applications.</h2><p>Safely handling error reports is a central aspect of software development, especially in safety-critical applications. CWE-778 describes a vulnerability caused by inadequate control over error reports. This post will analyse the risks associated with CWE-778 and show how developers can implement safe error-handling practices to avoid such vulnerabilities in Java programs.</p><ol><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#learn-how-inadequate-control-over-error-reporting-leads-to-security-vulnerabilities-and-how-to-prevent-them-in-java-applications">Learn how inadequate control over error reporting leads to security vulnerabilities and how to prevent them in Java applications.</a></li><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#what-is-cwe-778">What is CWE-778?</a><ol><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#examples-of-cwe-778-in-java">Examples of CWE-778 in Java</a></li></ol></li><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#secure-error-handling">Secure error handling</a><ol><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#example-with-vaadin-flow">Example with Vaadin Flow</a></li></ol></li><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#using-design-patterns-to-reuse-logging-and-error-handling">Using design patterns to reuse logging and error handling.</a><ol><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#decorator-pattern">Decorator Pattern</a></li><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#proxy-pattern">Proxy Pattern</a></li><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#template-method-pattern">Template Method Pattern</a></li></ol></li><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#evaluate-log-messages-for-attack-detection-in-real-time">Evaluate log messages for attack detection in real time</a></li><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#best-practices-for-avoiding-cwe-778">Best practices for avoiding CWE-778</a></li><li><a href="https://svenruppert.com/2024/10/18/auto-draft/#conclusion">Conclusion</a></li></ol><h2 id="what-is-cwe-778">What is CWE-778?</h2><p>The Common Weakness Enumeration (CWE) defines CWE-778 as a vulnerability where bug reporting is inadequately controlled. Bug reports often contain valuable information about an application&rsquo;s internal state, including system paths, configuration details, and other sensitive information that attackers can use to identify and exploit vulnerabilities. Improper handling of error reports can result in unauthorised users gaining valuable insight into the application&rsquo;s system structure and logic.</p><p>Exposing such information in a security-sensitive application could have potentially serious consequences, such as the abuse of SQL injection or cross-site scripting (XSS) vulnerabilities. Therefore, it is critical that bug reports are carefully controlled and only accessible to authorised individuals.</p><h3 id="examples-of-cwe-778-in-java">Examples of CWE-778 in Java</h3><p>The following example considers a simple Java application used to authenticate users:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">UserLogin</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">authenticateUser</span><span class="p">(</span><span class="s">"admin"</span><span class="p">,</span><span class="w"/><span class="s">"wrongpassword"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Error is output directly to the user</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Error: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">authenticateUser</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">"correctpassword"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">password</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Exception</span><span class="p">(</span><span class="s">"Invalid password for user: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">username</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This example displays an error message if the user enters an incorrect password. However, this approach has serious security gaps:</p><p>1. The error message contains specific information about the username.</p><p>2. The full stack trace is output, allowing an attacker to obtain details about the application&rsquo;s implementation.</p><p>This information can help an attacker understand the application&rsquo;s internal structure and make it easier for them to search specifically for additional vulnerabilities.</p><h2 id="secure-error-handling">Secure error handling</h2><p>To minimise the risks described above, secure error handling should be implemented. Instead of providing detailed information about the error, the user should only be shown a general error message:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">UserLogin</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">authenticateUser</span><span class="p">(</span><span class="s">"admin"</span><span class="p">,</span><span class="w"/><span class="s">"wrongpassword"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Generic error message to the user</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Authentication failed. Please check your entries."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Logging the error in the log file (for admins)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">logError</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">authenticateUser</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">"correctpassword"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">password</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Exception</span><span class="p">(</span><span class="s">"Invalid password for user: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">username</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">logError</span><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Error is securely logged without displaying it to the user</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">err</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"An error has occurred: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this improved version, only a general error message is displayed to the user while the error is logged internally. This prevents sensitive information from being shared with unauthorised users.</p><p>Such errors should be logged in a log file accessible only to authorised persons. A logging framework such as Log4j or SLF4J provides additional mechanisms to ensure logging security and store only necessary information.</p><h3 id="example-with-vaadin-flow">Example with Vaadin Flow</h3><p>Vaadin Flow is a Java framework for building modern web applications, and CWE-778 can also be a problem if error reports are mishandled. A safe example of error handling in a Vaadin application could look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.button.Button</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.notification.Notification</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.textfield.PasswordField</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.component.textfield.TextField</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.vaadin.flow.router.Route</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="s">"login"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">LoginView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">LoginView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">TextField</span><span class="w"/><span class="n">usernameField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">(</span><span class="s">"Benutzername"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">PasswordField</span><span class="w"/><span class="n">passwordField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">PasswordField</span><span class="p">(</span><span class="s">"Password"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Button</span><span class="w"/><span class="n">loginButton</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Login"</span><span class="p">,</span><span class="w"/><span class="n">event</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">authenticateUser</span><span class="p">(</span><span class="n">usernameField</span><span class="p">.</span><span class="na">getValue</span><span class="p">(),</span><span class="w"/><span class="n">passwordField</span><span class="p">.</span><span class="na">getValue</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// Generic error message to the user</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Notification</span><span class="p">.</span><span class="na">show</span><span class="p">(</span><span class="s">"Authentication failed. Please check your entries."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="c1">// Logging the error in the log file (for admins)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">logError</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">add</span><span class="p">(</span><span class="n">usernameField</span><span class="p">,</span><span class="w"/><span class="n">passwordField</span><span class="p">,</span><span class="w"/><span class="n">loginButton</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">authenticateUser</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">"correctpassword"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">password</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Exception</span><span class="p">(</span><span class="s">"Invalid password for user: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">username</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">logError</span><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Error is securely logged without displaying it to the user</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">err</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"An error has occurred: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The<code>logError</code> method ensures that errors are logged securely without sensitive information being visible to the end user. Vaadin Flow enables the integration of such secure practices to ensure that bug reports are not leaked uncontrollably.</p><h2 id="using-design-patterns-to-reuse-logging-and-error-handling">Using design patterns to reuse logging and error handling.</h2><p>To promote the reuse of error handling and logging, design patterns that enable the modularization and unification of such tasks can be used. Two suitable patterns are the<strong>Decorator Pattern</strong> and the<strong>Template Method Pattern</strong>.</p><h3 id="decorator-pattern">Decorator Pattern</h3><p>The Decorator Pattern is a structural design pattern that allows an object&rsquo;s functionality to be dynamically extended without changing the underlying class. This is particularly useful when adding additional responsibilities, such as logging, security checks, or error handling, without modifying the original class&rsquo;s code.</p><p>The Decorator Pattern works by using so-called “wrappers”. Instead of modifying the class directly, the object is wrapped in another class that implements the same interface and adds additional functionality. In this way, different decorators can be combined to create a flexible and expandable structure.</p><p>A vital feature of the Decorator Pattern is its adherence to the open-closed principle, one of the fundamental principles of object-oriented design. The open-closed principle states that a software component should be open to extensions but closed to modifications. The Decorator Pattern does just that by allowing classes to gain new functionality without changing their source code.</p><p>In the context of error handling and logging, developers can write an introductory class for authentication, while separate decorators handle error logging and handling of specific errors. This leads to a clear separation of responsibilities, significantly improving code maintainability.</p><p>The following example shows the implementation of the Decorator pattern to reuse error handling and logging:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">Authenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">void</span><span class="w"/><span class="nf">authenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">BasicAuthenticator</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Authenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">authenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">"correctpassword"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">password</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Exception</span><span class="p">(</span><span class="s">"Invalid password for user: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">username</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">LoggingAuthenticatorDecorator</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Authenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Authenticator</span><span class="w"/><span class="n">wrapped</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">LoggingAuthenticatorDecorator</span><span class="p">(</span><span class="n">Authenticator</span><span class="w"/><span class="n">wrapped</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">wrapped</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">wrapped</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">authenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">wrapped</span><span class="p">.</span><span class="na">authenticate</span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">password</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">logError</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="n">e</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">logError</span><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">err</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"An error has occurred: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example,<code>**BasicAuthenticator**</code> is used as the primary authentication class, while the<code>**LoggingAuthenticatorDecorator**</code> Added additional functionality, namely error logging. This decorator wraps the original authentication class and extends its behaviour. This allows the logic to be flexibly extended by adding more decorators, such as a<code>**SecurityCheckDecorator**</code>, which performs additional security checks before authentication.</p><p>An advantage of this approach is combining decorators in any order to achieve tailored functionality. For example, one could first add a security decoration and then implement error logging without changing the original authentication logic. This results in a flexible and reusable structure that is particularly useful in large projects where different aspects such as logging, security checks, and error handling are required in various combinations.</p><p>The Decorator Pattern is, therefore, a powerful tool for increasing software modularity and extensibility. It avoids code duplication, promotes reusability, and enables a clean separation of core logic and additional functionalities. This makes it particularly useful in secure error handling and implementing cross-cutting concerns such as logging in safety-critical applications.</p><p>The Decorator Pattern can add functionality, such as logging or error handling, to existing methods without modifying their original code. The following example shows how the Decorator Pattern enables centralised error handling:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">Authenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">void</span><span class="w"/><span class="nf">authenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">BasicAuthenticator</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Authenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">authenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">"correctpassword"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">password</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Exception</span><span class="p">(</span><span class="s">"Invalid password for user: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">username</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">LoggingAuthenticatorDecorator</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Authenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Authenticator</span><span class="w"/><span class="n">wrapped</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">LoggingAuthenticatorDecorator</span><span class="p">(</span><span class="n">Authenticator</span><span class="w"/><span class="n">wrapped</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">wrapped</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">wrapped</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">authenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">wrapped</span><span class="p">.</span><span class="na">authenticate</span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">password</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">logError</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="n">e</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">logError</span><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">err</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"An error has occurred: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the<code>**LoggingAuthenticatorDecorator**</code> is a decorator for the class<code>**BasicAuthenticator**</code>. The Decorator Pattern allows error handling and logging to be centralised without changing the underlying authentication class.</p><h3 id="proxy-pattern">Proxy Pattern</h3><p>The proxy pattern is a structural design pattern used to control access to an object. It is often used to add functionality such as caching, access control, or logging. In contrast to the decorator pattern, primarily used to extend functionality, the proxy serves as a proxy that takes control of access to the actual object.</p><p>The proxy pattern ensures that all access to the original object occurs via the proxy, meaning specific actions can be carried out automatically. For example, the Proxy Pattern could ensure that authorised users can only access a particular resource while logging all accesses.</p><p>A typical example of the proxy pattern for encapsulating logging and error handling looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">Authenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kt">void</span><span class="w"/><span class="nf">authenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">BasicAuthenticator</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Authenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">authenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">"correctpassword"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">password</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Exception</span><span class="p">(</span><span class="s">"Invalid password for user: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">username</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ProxyAuthenticator</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Authenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Authenticator</span><span class="w"/><span class="n">realAuthenticator</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">ProxyAuthenticator</span><span class="p">(</span><span class="n">Authenticator</span><span class="w"/><span class="n">realAuthenticator</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">this</span><span class="p">.</span><span class="na">realAuthenticator</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">realAuthenticator</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">authenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">logAccessAttempt</span><span class="p">(</span><span class="n">username</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">realAuthenticator</span><span class="p">.</span><span class="na">authenticate</span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">password</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">logError</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="n">e</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">logAccessAttempt</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Authentication attempt for user: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">username</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">logError</span><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">err</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"An error has occurred: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example,<code>BasicAuthenticator</code> is wrapped by a<code>**ProxyAuthenticator**</code>, which controls all calls to the<code>authenticate</code> method. The proxy adds additional functionality, such as access and error logging, ensuring that all access goes through the proxy before the authentication object is called.</p><p>A key difference between the Proxy Pattern and the Decorator Pattern is that the Proxy primarily controls access to the object and its use. The proxy can check access rights, add caching, or manage an object&rsquo;s lifetime. The Decorator Pattern, on the other hand, is designed to extend an object&rsquo;s behaviour by adding additional responsibilities without changing the access logic.</p><p>In other words, the Proxy Pattern acts as a protection or control mechanism, while the Decorator Pattern adds additional functionality to extend the behaviour. Both patterns are very useful when integrating cross-cutting concerns such as logging or security checks into the application, but they differ in their focus and application.</p><h3 id="template-method-pattern">Template Method Pattern</h3><p>The Template Method Pattern allows for defining the general flow of a process while implementing specific steps in subclasses. This ensures that error handling remains consistent:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">abstract</span><span class="w"/><span class="kd">class</span><span class="nc">AbstractAuthenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">authenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">doAuthenticate</span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">password</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">logError</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RuntimeException</span><span class="p">(</span><span class="s">"Authentication failed. Please check your entries."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">protected</span><span class="w"/><span class="kd">abstract</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">doAuthenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">logError</span><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">err</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"An error has occurred: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ConcreteAuthenticator</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">AbstractAuthenticator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">protected</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">doAuthenticate</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">username</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">password</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="s">"correctpassword"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">password</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Exception</span><span class="p">(</span><span class="s">"Invalid password for user: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">username</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The Template Method Pattern centralises error handling in the<code>**AbstractAuthenticator**</code> class so that all subclasses use the same consistent error handling strategy.</p><h2 id="evaluate-log-messages-for-attack-detection-in-real-time">Evaluate log messages for attack detection in real time</h2><p>Another aspect of secure error handling is using log messages to detect attacks in real-time. Analysing the log data can identify potential attacks early, and appropriate measures can be taken. The following approaches are helpful:</p><p><strong>Centralised logging</strong> : Use a central logging platform like the ELK Stack (Elasticsearch, Logstash, Kibana) or Splunk to collect all log data in one place. This enables comprehensive analysis and monitoring of security-related incidents.</p><p><strong>Pattern recognition</strong> : Create rules and patterns that identify potentially malicious activity, such as multiple failed login attempts in a short period. Such rules can trigger automated alerts when suspicious activity is detected.</p><p><strong>Anomaly detection</strong> : Machine learning techniques detect anomalous activity in log data. A sudden increase in certain error messages or unusual access patterns could indicate an ongoing attack.</p><p><strong>Real-time alerts</strong> : Configure the system so that certain security-related events trigger alerts in real-time. This allows administrators to respond immediately to potential threats.</p><p><strong>Analyse threat intelligence</strong> : Use log messages to collect and analyse threat intelligence. For example, IP addresses that repeatedly engage in suspicious activity can be identified, and appropriate action can be taken, such as blocking the address.</p><p><strong>Integration into SIEM systems</strong> : Use security information and event management (SIEM) systems to correlate log data from different sources and gain deeper insights into potential threats. SIEM systems often also provide tools to automate responses to specific events.</p><p>By combining these approaches, attacks can be detected early, and the necessary steps can be taken to limit the damage.</p><h2 id="best-practices-for-avoiding-cwe-778">Best practices for avoiding CWE-778</h2><p>To avoid CWE-778 in your applications, the following best practices should be followed:</p><p><strong>Generic error messages</strong> : Avoid sharing detailed information about errors with end users. Error messages should be worded as generally as possible to avoid providing clues about the internal implementation.</p><p><strong>Error logging</strong> : Use logging frameworks like Log4j or SLF4J to log errors securely. This allows bugs to be tracked internally without exposing sensitive information.</p><p><strong>No stack traces to users</strong> : Make sure stack traces are only visible in the log and are not output to the user. Instead, generic error messages that do not contain technical details should be used.</p><p><strong>Access control</strong> : Ensure that only authorized users have access to detailed error reports. Error logs should be well-secured and viewable only by administrators or developers.</p><p><strong>Regular error testing and security analysis</strong> : Run regular tests to ensure that error handling works correctly. Static code analysis tools help detect vulnerabilities like CWE-778 early.</p><p><strong>Avoiding sensitive information</strong> : To prevent sensitive information such as usernames, passwords, file paths, or server details from being included in error messages, such information should only be stored in secured log files.</p><p><strong>Using secure libraries</strong> : Rely on proven libraries and frameworks for error handling and logging that have already undergone security checks. This reduces the likelihood of implementation errors compromising security.</p><h2 id="conclusion">Conclusion</h2><p>CWE-778 poses a severe security threat if bug reports are not adequately controlled. Developers must know the importance of handling errors securely to prevent unwanted information leaks. Applying secure programming practices, such as using design patterns to reuse error-handling logic and implementing centralised logging to detect attacks in real-time, can significantly increase the security and robustness of Java applications.</p><p>Secure error handling improves an application&rsquo;s robustness and user experience by providing clear and useful instructions without overwhelming the user with technical details. The combination of security and usability is essential for the success and security of modern applications.</p><p>Ultimately, control over bug reports is integral to a software project&rsquo;s overall security strategy. Bug reports can either be a valuable resource for developers or, if handled poorly, become a vulnerability for attackers to exploit. Disciplined error handling, modern design patterns, and attack detection technologies are critical to ensuring that error reports are used as a tool for improvement rather than a vulnerability.</p>
]]></content:encoded><category>Design Pattern</category><category>Java</category><category>Secure Coding Practices</category><category>Security</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2024/10/090554C2-80F2-4F66-9A9B-B7205DF586C8.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/10/090554C2-80F2-4F66-9A9B-B7205DF586C8.jpg"/><enclosure url="https://svenruppert.com/images/2024/10/090554C2-80F2-4F66-9A9B-B7205DF586C8.jpg" type="image/jpeg" length="0"/></item><item><title>Code security through unit testing: The role of secure coding practices in the development cycle</title><link>https://svenruppert.com/posts/code-security-through-unit-testing-the-role-of-secure-coding-practices-in-the-development-cycle/</link><pubDate>Wed, 16 Oct 2024 22:17:04 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/code-security-through-unit-testing-the-role-of-secure-coding-practices-in-the-development-cycle/</guid><description>Unit testing is an essential software development concept that improves code quality by ensuring that individual units or components of a software function correctly. Unit testing is crucial in Java, one of the most commonly used programming languages. This article will discuss what unit testing is, how it has evolved, and what tools and best practices have been established over the years.</description><content:encoded>&lt;![CDATA[<p>Unit testing is an essential software development concept that improves code quality by ensuring that individual units or components of a software function correctly. Unit testing is crucial in Java, one of the most commonly used programming languages. This article will discuss what unit testing is, how it has evolved, and what tools and best practices have been established over the years.</p><ol><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#definition-von-unit-testing">Definition von Unit Testing</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#the-history-of-unit-testing">The history of unit testing</a><ol><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#the-1990s-and-the-emergence-of-junit">The 1990s and the emergence of JUnit</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#the-2000s-agile-methods-and-test-driven-development-tdd">The 2000s: Agile Methods and Test-Driven Development (TDD)</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#the-2010s-integration-into-ci-cd-pipelines-and-the-importance-of-automation">The 2010s: Integration into CI/CD pipelines and the importance of automation</a></li></ol></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#tools-and-frameworks-for-unit-testing-in-java">Tools and frameworks for unit testing in Java</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#best-practices-for-unit-testing-in-java">Best practices for unit testing in Java</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#the-future-of-unit-testing">The future of unit testing</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#what-are-the-disadvantages-of-unit-testing">What are the disadvantages of unit testing?</a><ol><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#time-expenditure-and-costs">Time expenditure and costs</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#limited-test-coverage">Limited test coverage</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#false-sense-of-security">False sense of security</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#excessive-mocking">Excessive mocking</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#difficulties-in-testability-of-legacy-code">Difficulties in testability of legacy code</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#not-suitable-for-complex-test-cases">Not suitable for complex test cases</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#test-driven-development-tdd-can-lead-to-excessive-focus-on-details">Test-driven development (TDD) can lead to excessive focus on details</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#test-maintenance-for-rapid-changes">Test maintenance for rapid changes</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#lack-of-testing-strategies-in-complex-systems">Lack of testing strategies in complex systems</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#additional-effort-without-immediate-benefit">Additional effort without immediate benefit</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#conclusion">Conclusion</a></li></ol></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#does-secure-pay-load-testing-belong-to-the-area-of-unit-testing">Does Secure Pay Load Testing belong to the area of ​​unit testing?</a><ol><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#what-is-secure-payload-testing">What is Secure Payload Testing?</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#difference-between-unit-testing-and-secure-payload-testing">Difference between Unit Testing and Secure Payload Testing</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#where-does-secure-payload-testing-fit-in">Where does Secure Payload Testing fit in?</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#can-secure-payload-testing-be-part-of-unit-testing">Can Secure Payload Testing be part of Unit Testing?</a></li></ol></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#which-secure-coding-practices-play-a-role-in-connection-with-unit-testing">Which secure coding practices play a role in connection with unit testing?</a><ol><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#input-validation-and-sanitisation">Input validation and sanitisation</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#limit-value-analysis-boundary-testing">Limit value analysis (boundary testing)</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#secure-error-handling">Secure error handling</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#avoiding-hard-coded-secrets">Avoiding hard-coded secrets</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#use-of-safe-libraries-and-dependencies">Use of safe libraries and dependencies</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#ensure-encryption">Ensure encryption</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#least-privilege-principle">Least Privilege Principle</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#avoiding-race-conditions">Avoiding race conditions</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#avoidance-of-buffer-overflows">Avoidance of buffer overflows</a></li><li><a href="https://svenruppert.com/2024/10/16/auto-draft/#safe-use-of-third-party-libraries">Safe use of third-party libraries</a></li></ol></li></ol><h2 id="definition-von-unit-testing">Definition von Unit Testing</h2><p><strong>Unit Testing</strong> refers to testing a program&rsquo;s most minor functional units—usually individual functions or methods. A unit test ensures that a specific code function works as expected and typically checks whether a method or class returns the correct output for particular inputs.</p><p>In Java, unit testing often refers to testing individual methods in a class, testing whether the code responds as expected to both average and exceptional input.</p><p>Example in Java:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Calculator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="nf">add</span><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">a</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">b</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="n">a</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">b</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>A unit test for the<code>add</code> method could look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">CalculatorTest</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Test</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">testAdd</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Calculator</span><span class="w"/><span class="n">calc</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Calculator</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">assertEquals</span><span class="p">(</span><span class="n">5</span><span class="p">,</span><span class="w"/><span class="n">calc</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">2</span><span class="p">,</span><span class="w"/><span class="n">3</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This example tests whether the<code>add</code> method correctly combines two numbers. If the test is successful, the process works as expected.</p><h2 id="the-history-of-unit-testing">The history of unit testing</h2><p>The roots of unit testing lie in the early days of software development. As early as the 1960s, programmers recognised that it was essential to ensure that individual parts of a program were working correctly before integrating the entire system. However, unit testing only became primarily standardised in the 1990s with the spread of object-oriented programming languages ​​such as Java and C++.</p><h3 id="the-1990s-and-the-emergence-of-junit">The 1990s and the emergence of JUnit</h3><p>One of the most influential events in the history of unit testing was the introduction of the framework<strong>JUnit</strong> for the Java programming language in 1999. Developed by Kent Beck and Erich Gamma, JUnit revolutionised how Java developers test their software.</p><p>JUnit enabled developers to write simple and easy-to-maintain unit tests for their Java programs. Before JUnit, no widely used, standardised tools facilitated unit testing in Java. Programmers had to create their test suites manually, which was tedious and error-prone.</p><p>JUnit allowed developers to automate tests, create test suites, and integrate testing into their development workflow. Its introduction contributed significantly to raising awareness about the importance of testing and promoting the adoption of unit testing in the industry.</p><h3 id="the-2000s-agile-methods-and-test-driven-development-tdd">The 2000s: Agile Methods and Test-Driven Development (TDD)</h3><p>In the early 2000s, agile development methodologies gained popularity, particularly<strong>Extreme Programming (XP)</strong> and<strong>Scrum</strong>. These methods emphasized short development cycles, continuous integration, and, most importantly, testing. One of the core ideas of XP is<strong>Test-Driven Development (TDD)</strong> , a methodology in which tests are created before actual code is written.</p><p>In TDD, the development process is controlled by tests:</p><p>1. First, the developer writes a unit test that fails because the function to be tested still needs to be implemented.</p><p>2. The minimal code required to pass the test is then written.</p><p>3. Finally, the code is refined and optimised, with testing ensuring that existing functionality is not affected.</p><p>TDD requires developers to engage intensively with unit testing, which drives the entire development process. Unit tests are not just a way to ensure quality but an integral part of the design.</p><p>JUnit played a central role during this time, making implementing TDD in Java much more accessible. Developers could write and run tests quickly and easily, making the workflow more efficient.</p><h3 id="the-2010s-integration-into-cicd-pipelines-and-the-importance-of-automation">The 2010s: Integration into CI/CD pipelines and the importance of automation</h3><p>With the advent of<strong>Continuous Integration (CI)</strong> and<strong>Continuous Delivery (CD)</strong> In the 2010s, unit testing became even more critical. CI/CD pipelines allow developers to continuously integrate changes to the code base into the central repository and automatically deploy new versions of their software.</p><p>In a CI/CD environment, automated testing, including unit testing, is critical to ensure that new code changes do not break existing functionality. Unit tests are typically the first stage of testing executed in a CI pipeline because they are fast and focused.</p><p>For this purpose, numerous tools have been created that integrate unit tests into the CI/CD workflow. Besides JUnit, various build tools, like<strong>Maven</strong> and<strong>Gradle</strong> , can run unit tests automatically. Test reports are generated, and the developer is notified if a test fails.</p><p>Another significant advancement during this time was integrating<strong>code coverage tools</strong> like<strong>JaCoCo</strong>. These tools measure the percentage of code covered by unit tests and help developers ensure that their tests cover all relevant code paths.</p><h2 id="tools-and-frameworks-for-unit-testing-in-java">Tools and frameworks for unit testing in Java</h2><p><strong>JUnit</strong> : Probably the best-known and most widely used testing framework for Java. Since its introduction in 1999, JUnit has evolved and offers numerous features that make testing easier. With the introduction of JUnit 5, the framework became more modular and flexible.</p><p><strong>TestNG</strong> : Another popular testing framework that offers similar functionality to JUnit but supports additional features such as dependency management and parallel test execution. TestNG is often used in larger projects that require complex testing scenarios.</p><p><strong>Mockito</strong> : A mocking framework used to simulate dependencies of classes in unit tests. Mockito allows developers to “mock” objects to control the behaviour of dependencies without actually instantiating them.</p><p><strong>JaCoCo</strong> : A tool for measuring code coverage. It shows what percentage of the code is covered by tests and helps developers identify untested areas in the code.</p><p><strong>Gradle and Maven</strong> : These build tools provide native support for unit testing. They allow developers to run tests and generate reports during the build process automatically.</p><h2 id="best-practices-for-unit-testing-in-java">Best practices for unit testing in Java</h2><p><strong>Isolated testing</strong> : Each unit test should test a single method or function in isolation. This means that external dependencies such as databases, networks or file systems should be “mocked” to ensure that the test focuses only on the code under test.</p><p><strong>Write simple tests</strong> : Unit tests should be simple and easy to understand. You should only test a single functionality and not have any complex logic or dependencies.</p><p><strong>Regular testing</strong> : Tests should be conducted regularly, especially before every commit or build. This ensures that errors are detected early and that the code remains stable.</p><p><strong>Test cases for limits</strong> : It is essential to test typical use cases and cover limit values ​​(e.g., minimum and maximum input values) and error cases.</p><p><strong>Ensure test coverage</strong> : Code coverage tools like JaCoCo help developers ensure that most of the code is covered by tests. However, high test coverage should be one of many goals. The quality of the tests is just as important as the quantity.</p><h2 id="the-future-of-unit-testing">The future of unit testing</h2><p>Unit testing has constantly evolved over the past few decades, and this development is expected to continue in the future. The advent of technologies like<strong>Artificial Intelligence (AI)</strong> and<strong>Machine Learning</strong> could open up new ways of generating and executing unit tests.</p><p>AI-powered tools could automatically generate unit tests by analysing the code and suggesting tests based on typical patterns. This could further automate the testing process and give developers more time for actual development.</p><p>Another trend is the increasing integration of<strong>Mutation Testing</strong>. This technique involves making minor changes to the code to check whether the existing tests detect the mutated code paths and throw errors. This allows developers to ensure that their tests cover code and detect errors.</p><p>Unit testing is an essential part of modern software development and has evolved from an optional practice to a standard. In Java, JUnit has contributed significantly to the proliferation and standardisation of unit testing. At the same time, agile methodologies such as TDD have integrated the role of testing into the development process.</p><p>With increasing automation through CI/CD pipelines and the availability of powerful tools, unit testing will continue to play a critical role in ensuring code quality in the future. Developers integrating unit testing into their workflow early on benefit from stable, maintainable and error-free code.</p><h2 id="what-are-the-disadvantages-of-unit-testing">What are the disadvantages of unit testing?</h2><p>Unit testing has many advantages, especially ensuring code quality and finding errors early. However, there are also some disadvantages and limitations that must be taken into account in practice:</p><h3 id="time-expenditure-and-costs">Time expenditure and costs</h3><p><strong>Creation and maintenance</strong> : Writing unit tests requires additional time, especially in the early stages of a project. This effort increases development costs and can seem disproportionate for small projects.</p><p><strong>Maintenance</strong> : When the code changes, the associated tests often also need to be adapted. This can be very time-consuming for larger code bases. Changes to the design or architecture can lead to numerous test changes.</p><h3 id="limited-test-coverage">Limited test coverage</h3><p><strong>Only tests isolated units</strong> : Unit tests test individual functions or methods in isolation. They do not cover the interaction of different modules or layers of the system, which means that they cannot reveal integration problems.</p><p><strong>Not suitable for all types of errors</strong> : Unit tests are good at finding logical errors in individual methods, but they cannot detect other types of errors, such as errors in interaction with databases, networks, or the user interface.</p><h3 id="false-sense-of-security">False sense of security</h3><p><strong>High test coverage ≠ freedom from errors</strong> : Developers may develop a false sense of security when achieving high test coverage. Just because many tests cover the code doesn&rsquo;t mean it&rsquo;s bug-free. Unit tests only cover the specific code they were written and may not test all edge or exception cases.</p><p><strong>Blind trust in testing</strong> : Sometimes, developers rely too heavily on unit testing and neglect other types of testing, such as integration testing, system testing, or manual testing.</p><h3 id="excessive-mocking">Excessive mocking</h3><p><strong>Mocks can distort reality</strong> : When testing classes or methods that depend on external dependencies (e.g. databases, APIs, file systems), mock objects are often used to simulate these dependencies. However, extensive mocking frameworks such as Mockito can lead to unrealistic tests that work differently than the system in a real environment.</p><p><strong>Complex dependencies</strong> : When a class has many dependencies, creating mocks can become very complicated, making the tests difficult to understand and maintain.</p><h3 id="difficulties-in-testability-of-legacy-code">Difficulties in testability of legacy code</h3><p><strong>Legacy-Code ohne Tests</strong> : Writing unit tests can be challenging and time-consuming in existing projects with older code (legacy code). Such systems may have been designed without testability in mind, making unit tests difficult to write.</p><p><strong>Refactoring necessary</strong> : Legacy code often needs to be refactored to enable unit testing, which can introduce additional risks and costs.</p><h3 id="not-suitable-for-complex-test-cases">Not suitable for complex test cases</h3><p><strong>Not suitable for end-to-end testing</strong> : Unit tests are designed to test individual units and are not intended to cover end-to-end test cases or user interactions. Such testing requires other types of testing, such as integration, system, or acceptance testing.</p><p><strong>Limited perspective</strong> : Unit tests often only consider individual system components and not the behaviour of the entire system in real usage scenarios.</p><h3 id="test-driven-development-tdd-can-lead-to-excessive-focus-on-details">Test-driven development (TDD) can lead to excessive focus on details</h3><p><strong>Design of code influenced by testing</strong> : In TDD, the emphasis is on writing tests before writing the code. This can sometimes lead to developers designing code to pass tests rather than developing a more general, robust solution.</p><p><strong>Excessive focus on detailed testing</strong> : TDD can cause developers to focus too much on small details and isolated components instead of considering the overall system architecture and user needs.</p><h3 id="test-maintenance-for-rapid-changes">Test maintenance for rapid changes</h3><p><strong>Frequent changes lead to outdated tests</strong> : In fast-paced development projects where requirements and code change frequently, tests can quickly become obsolete and unnecessary. Maintaining these tests can become significant without providing any clear added value.</p><p><strong>Tests as ballast</strong> : If code is constantly evolving and tests are not updated, outdated or irrelevant tests can burden the development process.</p><h3 id="lack-of-testing-strategies-in-complex-systems">Lack of testing strategies in complex systems</h3><p><strong>Complexity of test structure</strong> : It can be difficult to develop a meaningful unit testing strategy that covers all aspects of very complex systems. This often results in fragmented and incomplete tests or inadequate testing of critical areas of the system.</p><p><strong>Testing complexity in object-oriented designs</strong> : For highly object-oriented programs, it can be difficult to identify precise units for unit testing, especially if the classes are highly interconnected. In such cases, writing unit tests can become cumbersome and inefficient.</p><h3 id="additional-effort-without-immediate-benefit">Additional effort without immediate benefit</h3><p><strong>Cost-benefit analysis for small projects</strong> : In small projects or prototypes where the code is short-lived, the effort spent writing unit tests may outweigh the benefits. In such cases, other testing methods, such as manual or simple end-to-end testing, may be more efficient.</p><h3 id="conclusion">Conclusion</h3><p>Although unit testing has numerous advantages, there are also clear disadvantages that must be considered. The additional time required, limited test coverage, and potential maintenance challenges must be weighed when planning a testing process. Unit testing is not a panacea but should be viewed as one tool among many in software development. However, combined with other types of testing and a well-thought-out testing strategy, unit testing can significantly improve code quality.</p><h2 id="does-secure-pay-load-testing-belong-to-the-area-of-unit-testing">Does Secure Pay Load Testing belong to the area of ​​unit testing?</h2><p><strong>Secure Payload Testing</strong> usually belongs to a different area than the traditional area of<strong>Unit Testing,</strong> which is<strong>Security testing</strong> and partial<strong>Integration Tests</strong>. Let&rsquo;s take a closer look at this to better understand the boundaries.</p><h3 id="what-is-secure-payload-testing">What is Secure Payload Testing?</h3><p><strong>Secure Payload Testing</strong> refers to testing security-related data or messages exchanged between system components. This particularly applies to scenarios where sensitive data (such as passwords, API keys, encrypted data, etc.) must be protected and handled correctly in communication between systems. It tests whether data is appropriately encrypted, decrypted and authenticated during transmission and whether there are any potential security gaps in handling this data.</p><p>Examples of typical questions in secure payload testing are:</p><ul><li>Is sensitive data encrypted and decrypted correctly?</li><li>Does data remain secure during transmission?</li><li>Is it ensured that the payload data does not contain security holes, such as SQL injections or cross-site scripting (XSS)?</li><li>Is the integrity of the payload guaranteed to prevent manipulation?</li></ul><h3 id="difference-between-unit-testing-and-secure-payload-testing">Difference between Unit Testing and Secure Payload Testing</h3><p><strong>Unit Testing</strong> focuses on the<strong>Functionality</strong> of individual program components or methods in isolation**.** It usually checks whether a method delivers the expected outputs for certain inputs. The focus is on the correctness and stability of the program&rsquo;s logical units, not directly on the security or protection of data.</p><p>An example of a unit test in Java would be testing a simple mathematical function. The unit test would check whether the method works correctly. Security aspects, such as handling confidential data or ensuring encryption, are usually outside of such tests.</p><p>In contrast,<strong>Secure Payload Testing</strong> involves the secure handling and processing of data during transmission or storage. This is often part of<strong>security testing</strong> , which aims to ensure that data is properly protected and not vulnerable to attacks or data leaks.</p><h3 id="where-does-secure-payload-testing-fit-in">Where does Secure Payload Testing fit in?</h3><p><strong>Integration tests</strong> : Secure Payload Testing could be part of integration testing, which tests the interaction between different components of a system, e.g., between a client and a server. Here, one would ensure that the payload is properly encrypted and the transmission over the network is secure.</p><p><strong>Security testing</strong> : In more complex systems, secure payload testing belongs more to<strong>Security testing</strong> , which involves attacks on data security, integrity, and confidentiality of payloads. These tests often go beyond the functionality of individual code units and require special testing strategies, such as penetration testing or testing for known security vulnerabilities.</p><p><strong>End-to-End Tests</strong> : Since Secure Payload Testing is often related to data transfer, it can also be part of<strong>End-to-End tests</strong>. The entire system is tested here, from input to processing to output. These tests check whether the data is encrypted correctly at the beginning and decrypted and processed correctly at the end.</p><h3 id="can-secure-payload-testing-be-part-of-unit-testing">Can Secure Payload Testing be part of Unit Testing?</h3><p>In special cases,<strong>an aspect</strong> of secure payload testing can be part of unit tests, especially if the security logic is very closely linked to the functionality of the unit under test (e.g., an encryption or decryption method).</p><p>An example could be testing an<strong>Encryption method</strong> in isolation:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">encrypt</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">data</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">key</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Encryption logic</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"/><span class="n">encryptedData</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">decrypt</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">encryptedData</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">key</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// Decryption logic</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"/><span class="n">decryptedData</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here, you could write unit tests that check whether:</p><ul><li>The text is correctly encrypted and decrypted.</li><li>For the same input data, the same output is always produced (in the case of deterministic encryption).</li><li>The method responds correctly to invalid inputs (e.g. wrong key, invalid data format).</li></ul><p>Despite these specific test cases, secure payload testing generally focuses on not only isolated functionality but also security and integrity in the context of other system components.</p><p><strong>Secure Payload Testing</strong> does not belong to classic<strong>Unit Testing.</strong> It typically concerns security, integrity, and correct data processing in a broader context, often involving the interaction of multiple system components. It falls more into the realm of<strong>security</strong> and<strong>integration tests</strong>.</p><p>However, unit tests can test parts of security logic to some extent, especially when encryption or security functions are to be tested in isolation. However, the overall picture of security requirements and protection of payloads is usually determined by more comprehensive testing, such as<strong>Integration Tests</strong> ,<strong>Security testing,</strong> or<strong>End-to-End tests</strong>.</p><h2 id="which-secure-coding-practices-play-a-role-in-connection-with-unit-testing">Which secure coding practices play a role in connection with unit testing?</h2><p><strong>Secure Coding Practices</strong> are essential to ensure the code is secure against potential attacks and vulnerabilities. These practices are fundamental to ensuring software security, and they can be closely related to<strong>Unit Testing</strong>. While unit testing primarily aims to verify code&rsquo;s functionality, secure coding practices can help ensure that code is robust and secure. Here are some of the key Secure Coding Practices related to Unit Testing:</p><h3 id="input-validation-and-sanitisation">Input validation and sanitisation</h3><p><strong>Safe practice</strong> : Always ensure that input from external sources (user input, API calls, file input) is validated and “sanitised” to avoid dangerous content such as SQL injection or cross-site scripting (XSS).</p><p><strong>Connection to unit testing</strong> : Unit tests should ensure that methods and functions respond correctly to invalid or potentially dangerous input. Tests should contain inputs such as unexpected special characters, entries that are too long or short, empty fields, or formatting errors.</p><p><strong>Example unit test in Java</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"> @Test</span></span><span class="line"><span class="cl">     public void testValidateUserInput() {</span></span><span class="line"><span class="cl">         String invalidInput = "<span class="nt">&lt;script&gt;</span>alert('xss')<span class="nt">&lt;/script&gt;</span>";</span></span><span class="line"><span class="cl">         assertFalse(InputValidator.isValid(invalidInput));</span></span><span class="line"><span class="cl">     }</span></span></code></pre></div></div><p>This test checks whether the<code>isValid</code> method correctly rejects unsafe input as invalid.</p><h3 id="limit-value-analysis-boundary-testing">Limit value analysis (boundary testing)</h3><p><strong>Safe practice</strong> : Inputs should be tested against their maximum and minimum limits to ensure the code does not crash or be vulnerable to buffer overflows.</p><p><strong>Connection to unit testing</strong> : Unit tests should ensure the application safely responds to input at the top and bottom of its allowed ranges. This helps prevent typical attacks such as buffer overflows.</p><p><strong>Example</strong> : If a function only accepts a certain number of characters, the unit test should check how the function responds to inputs that are precisely at or above that limit.</p><h3 id="secure-error-handling">Secure error handling</h3><p><strong>Safe practice</strong> : Error handling should not reveal sensitive information, such as stack traces or details about the application&rsquo;s internal structure, as attackers can exploit such information.</p><p><strong>Connection to unit testing</strong> : Unit tests should ensure errors and exceptions are handled correctly without exposing sensitive information. Unit tests can trigger targeted exception situations and check whether only safe and user-friendly error messages are returned.</p><p><strong>Example</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Test</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">testHandleInvalidInputGracefully</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">myService</span><span class="p">.</span><span class="na">processUserInput</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">fail</span><span class="p">(</span><span class="s">"Exception thrown, but should have been handled gracefully."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span></span></span></code></pre></div></div><h3 id="avoiding-hard-coded-secrets">Avoiding hard-coded secrets</h3><p><strong>Safe practice</strong> : Never hard-code sensitive information such as passwords, API keys, or tokens in code. Instead, such data should be stored in secure environment variables or configuration files.</p><p><strong>Connection to unit testing</strong> : Unit tests should ensure that sensitive data is handled securely. They should also check that the code loads external configuration sources correctly and does not accidentally use hard-coded secrets.</p><p><strong>Example</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Test</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">testExternalConfigForSecrets</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">assertNotNull</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="na">getenv</span><span class="p">(</span><span class="s">"API_KEY"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span></span></span></code></pre></div></div><h3 id="use-of-safe-libraries-and-dependencies">Use of safe libraries and dependencies</h3><p><strong>Safe practice</strong> : Users should take care to use secure libraries and frameworks and update them regularly to avoid known security vulnerabilities.</p><p><strong>Connection to unit testing</strong> : Unit tests should ensure the libraries are correctly integrated and updated. Testing functionality that depends on external libraries is also essential to ensure that security mechanisms in those libraries are used correctly.</p><h3 id="ensure-encryption">Ensure encryption</h3><p><strong>Safe practice</strong> : Sensitive data should be stored and transmitted in encrypted form to prevent unauthorised access or data leaks.</p><p><strong>Connection to unit testing</strong> : Unit tests should verify that data is encrypted and decrypted correctly. For example, a test could ensure that the encryption and decryption methods work consistently and without errors.</p><p><strong>Example</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Test</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">testEncryptionAndDecryption</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">String</span><span class="w"/><span class="n">original</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Sensitive Data"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">String</span><span class="w"/><span class="n">encrypted</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">encrypt</span><span class="p">(</span><span class="n">original</span><span class="p">,</span><span class="w"/><span class="s">"mySecretKey"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">String</span><span class="w"/><span class="n">decrypted</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">decrypt</span><span class="p">(</span><span class="n">encrypted</span><span class="p">,</span><span class="w"/><span class="s">"mySecretKey"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">assertEquals</span><span class="p">(</span><span class="n">original</span><span class="p">,</span><span class="w"/><span class="n">decrypted</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span></span></span></code></pre></div></div><h3 id="least-privilege-principle">Least Privilege Principle</h3><p><strong>Safe practice</strong> : Methods and functions should only be executed with the minimum rights and access required.</p><p><strong>Connection to unit testing</strong> : Unit tests should ensure that methods only work with the minimum required data and that no unauthorised access to resources is possible. For example, tests could check whether protected resources are only accessed after successful authentication.</p><p><strong>Example</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Test</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">testUnauthorizedAccess</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">assertThrows</span><span class="p">(</span><span class="n">AccessDeniedException</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/><span class="p">()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">userService</span><span class="p">.</span><span class="na">deleteUserDataWithoutPermission</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span></span></span></code></pre></div></div><h3 id="avoiding-race-conditions">Avoiding race conditions</h3><p><strong>Safe practice</strong> : Race conditions can occur when multiple threads or processes access shared resources simultaneously. They should be avoided to prevent security issues such as unpredictable behavior or data corruption.</p><p><strong>Connection to unit testing</strong> : Unit tests should ensure that the code is thread-safe and that no race conditions occur. This can be verified by testing code under multiple access or by using mock threads.</p><p><strong>Example</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Test</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">testConcurrentAccess</span><span class="p">()</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">CountDownLatch</span><span class="w"/><span class="n">latch</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">CountDownLatch</span><span class="p">(</span><span class="n">2</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">Runnable</span><span class="w"/><span class="n">task</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">()</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">sharedResource</span><span class="p">.</span><span class="na">modify</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="n">latch</span><span class="p">.</span><span class="na">countDown</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="p">};</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">Thread</span><span class="w"/><span class="n">t1</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Thread</span><span class="p">(</span><span class="n">task</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">Thread</span><span class="w"/><span class="n">t2</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Thread</span><span class="p">(</span><span class="n">task</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">t1</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">t2</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">latch</span><span class="p">.</span><span class="na">await</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="n">assertTrue</span><span class="p">(</span><span class="n">sharedResource</span><span class="p">.</span><span class="na">isConsistent</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span></span></span></code></pre></div></div><h3 id="avoidance-of-buffer-overflows">Avoidance of buffer overflows</h3><p><strong>Safe practice</strong> : Buffer overflows occur when a program writes more data into a memory area than it can hold. Although Java is less prone to buffer overflows than C or C++, thanks to automatic memory management, care should still be taken to ensure that arrays and memory structures are used safely.</p><p><strong>Connection to unit testing</strong> : Unit tests should test edge cases and maximum input values ​​to ensure that overflows do not occur.</p><h3 id="safe-use-of-third-party-libraries">Safe use of third-party libraries</h3><p><strong>Safe practice</strong> : Third-party libraries should be used safely and regularly checked for known vulnerabilities.</p><p><strong>Connection to unit testing</strong> : Tests can ensure that functions and classes from third-party libraries are implemented correctly and used safely. Mocking can be used to simulate external dependencies safely.</p><p><strong>Secure Coding Practices</strong> play an essential role in<strong>Unit Testing</strong> , as they ensure that the code is not only functionally correct but also secure against potential attacks. Unit tests should aim to check security-relevant aspects such as input validation, secure error handling, encryption, and rights assignment. By incorporating these practices into the unit testing process, developers can ensure their applications are robust, secure, and protected against many common attack patterns.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>TDD</category><media:content url="https://svenruppert.com/images/2024/10/F51BDF99-FA12-46E0-B373-6EB383248031.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/10/F51BDF99-FA12-46E0-B373-6EB383248031.jpg"/><enclosure url="https://svenruppert.com/images/2024/10/F51BDF99-FA12-46E0-B373-6EB383248031.jpg" type="image/jpeg" length="0"/></item><item><title>Understanding TOCTOU (Time-of-Check to Time-of-Use) in the Context of CWE-377</title><link>https://svenruppert.com/posts/understanding-toctou-time-of-check-to-time-of-use-in-the-context-of-cwe-377/</link><pubDate>Mon, 07 Oct 2024 17:57:36 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/understanding-toctou-time-of-check-to-time-of-use-in-the-context-of-cwe-377/</guid><description>Building on the discussion of “CWE-377: Insecure Temporary File”, it’s essential to delve deeper into one of the most insidious vulnerabilities that can arise in this context—TOCTOU (Time-of-Check to Time-of-Use) race conditions. TOCTOU vulnerabilities occur when there is a time gap between verifying a resource (such as a file) and its subsequent use. Malicious actors can exploit this gap, especially in temporary file scenarios, leading to serious security breaches. This follow-up article will explore how TOCTOU conditions manifest in software, particularly in managing temporary files, and discuss strategies to mitigate these risks to ensure robust and secure application development.</description><content:encoded>&lt;![CDATA[<p>Building on the discussion of “CWE-377: Insecure Temporary File”, it’s essential to delve deeper into one of the most insidious vulnerabilities that can arise in this context—TOCTOU (Time-of-Check to Time-of-Use) race conditions. TOCTOU vulnerabilities occur when there is a time gap between verifying a resource (such as a file) and its subsequent use. Malicious actors can exploit this gap, especially in temporary file scenarios, leading to serious security breaches. This follow-up article will explore how TOCTOU conditions manifest in software, particularly in managing temporary files, and discuss strategies to mitigate these risks to ensure robust and secure application development.</p><p><em>I wrote an article about CWE-377 itself. You can find it here:</em><a href="https://svenruppert.com/2024/08/21/cwe-377-insecure-temporary-file-in-java/">https://svenruppert.com/2024/08/21/cwe-377-insecure-temporary-file-in-java/</a></p><p><strong>TOCTOU (Time-of-Check to Time-of-Use)</strong> is a type of race condition that occurs when the state of a resource (such as a file, memory, or variable) is checked (validated or verified) and then used (modified or accessed) in separate steps. If an attacker can alter the resource between these two steps, they may exploit the gap to introduce malicious behaviour or compromise the security of an application.</p><h3 id="how-toctou-applies-to-temporary-files">How TOCTOU Applies to Temporary Files</h3><p>In the context of temporary file creation, TOCTOU vulnerabilities arise when the program checks whether a temporary file exists and then creates or opens it. If an attacker manages to create a file with the same name in the interval between these operations, they can control the contents or properties of the file the program thinks it is safely creating or accessing.</p><p>For example, consider the following sequence of operations:</p><p><strong>Check if a file with a specific name exists</strong> : The program checks if a temporary file (e.g.,<code>tempfile.txt</code>) already exists.</p><p><strong>Create or open the file</strong> : If the file does not exist, the program creates or opens it.</p><p>If an attacker creates a file named<code>tempfile.txt</code> in the time between the check and the creation, the program may inadvertently interact with the attacker&rsquo;s file instead of a legitimate, secure file. This can lead to issues such as unauthorised data access, corruption, or privilege escalation.</p><h4 id="detailed-example-of-toctou-vulnerability">Detailed Example of TOCTOU Vulnerability</h4><p>Consider the following Java code:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">TOCTOUVulnerabilityExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">File</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"/tmp/tempfile.txt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Time-of-check: Verify if the file exists</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">tempFile</span><span class="p">.</span><span class="na">exists</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Time-of-use: Create the file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">tempFile</span><span class="p">.</span><span class="na">createNewFile</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Temporary file created at: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">tempFile</span><span class="p">.</span><span class="na">getAbsolutePath</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example:</p><p>1. The program first checks if<code>**tempfile.txt**</code> exists using the<code>**exists()**</code> method.</p><p>2. If the file does not exist, it creates a new file with the same name using the<code>**createNewFile()**</code> method.</p><p>The vulnerability here lies between the time the<code>**exists()**</code> check is performed and the time the<code>**createNewFile()**</code> method is called. If an attacker creates a file named<code>**tempfile.txt**</code> between these two steps, the program will not create a new file but instead interact with the attacker&rsquo;s file, potentially leading to a security breach.</p><h4 id="exploitation-of-toctou-in-temporary-files">Exploitation of TOCTOU in Temporary Files</h4><p>An attacker can exploit TOCTOU in several ways:</p><p><strong>File Pre-Creation</strong> : The attacker creates a file with the same name as the intended temporary file in a directory accessible by the application. If the file permissions are weak, the attacker may gain control over the contents of this file.</p><p><strong>Symlink Attack</strong> : The attacker can create a symbolic link (symlink) that points to a sensitive file (e.g.,<code>/etc/passwd</code>) with the same name as the temporary file. When the program tries to write to or read from the temporary file, it might access the sensitive file instead, leading to data corruption or information leakage.</p><p><strong>Privilege Escalation</strong> : If the program runs with elevated privileges (e.g., as root), an attacker could exploit the TOCTOU race condition to modify files or data that they otherwise would not have permission to access or change.</p><h3 id="preventing-toctou-vulnerabilities-in-java">Preventing TOCTOU Vulnerabilities in Java</h3><p>To prevent TOCTOU vulnerabilities, particularly when dealing with temporary files, developers should follow best practices that minimise the risk of a race condition:</p><p><strong>Use Atomic Operations</strong></p><p>Atomic operations are inseparable; they either complete entirely or do not happen at all, leaving no opportunity for an attacker to intervene. Java&rsquo;s<code>File.createTempFile()</code> method is atomic when creating temporary files. This means that the file creation and name generation occur in a single step, eliminating the TOCTOU window.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">AtomicTempFileCreation</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Atomic operation to create a temporary file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">File</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">File</span><span class="p">.</span><span class="na">createTempFile</span><span class="p">(</span><span class="s">"tempfile_"</span><span class="p">,</span><span class="w"/><span class="s">".tmp"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">tempFile</span><span class="p">.</span><span class="na">deleteOnExit</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Secure temporary file created at: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">tempFile</span><span class="p">.</span><span class="na">getAbsolutePath</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here,<code>**File.createTempFile()**</code> ensures that the file is both uniquely named and securely created without exposing the application to race conditions.</p><p><strong>Use Secure Directories</strong></p><p>Place temporary files in a secure, private directory that is inaccessible to other users. This limits attackers&rsquo; opportunities to exploit TOCTOU vulnerabilities because they cannot easily place or manipulate files in these directories.</p><p><strong>Leverage<code>Files</code> and<code>Path</code> (NIO.2 API)</strong></p><p>Java’s NIO.2 API (<code>java.nio.file</code>) offers more advanced file-handling mechanisms, including atomic file operations. For instance,<code>**Files.createTempFile()**</code> allows for atomic file creation with customisable file attributes, such as secure permissions, further reducing the risk of TOCTOU vulnerabilities.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Files</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Path</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.attribute.PosixFilePermissions</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.Set</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SecureAtomicTempFile</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Create a temporary file with atomic operations and secure permissions</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Path</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">createTempFile</span><span class="p">(</span><span class="s">"secure_tempfile_"</span><span class="p">,</span><span class="w"/><span class="s">".tmp"</span><span class="p">,</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">PosixFilePermissions</span><span class="p">.</span><span class="na">asFileAttribute</span><span class="p">(</span><span class="n">PosixFilePermissions</span><span class="p">.</span><span class="na">fromString</span><span class="p">(</span><span class="s">"rw-------"</span><span class="p">)));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Secure temporary file created at: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">tempFile</span><span class="p">.</span><span class="na">toAbsolutePath</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This approach combines atomic file creation with restrictive file permissions, mitigating both TOCTOU vulnerabilities and other potential security risks.</p><h3 id="conclusion">Conclusion</h3><p>TOCTOU vulnerabilities represent a significant security risk when handling temporary files, particularly if these files are created in an insecure or non-atomic manner. The key to preventing these vulnerabilities lies in eliminating the gap between the time-of-check and time-of-use, typically by using atomic file creation methods provided by secure APIs, such as<code>**File.createTempFile()**</code> or<code>**Files.createTempFile()**</code>.</p><p>By understanding the risks associated with TOCTOU race conditions and following best practices, developers can ensure that their Java applications are resilient to these attacks and maintain the software&rsquo;s integrity and security.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Security</category><media:content url="https://svenruppert.com/images/2024/08/0ABA49EF-C21C-42F2-BA6B-260FD05F1672.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/08/0ABA49EF-C21C-42F2-BA6B-260FD05F1672.jpg"/><enclosure url="https://svenruppert.com/images/2024/08/0ABA49EF-C21C-42F2-BA6B-260FD05F1672.jpg" type="image/jpeg" length="0"/></item><item><title>BLD - a lightweight Java Build Tool</title><link>https://svenruppert.com/posts/bld-a-lightweight-java-build-tool/</link><pubDate>Thu, 26 Sep 2024 17:31:17 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/bld-a-lightweight-java-build-tool/</guid><description>What is a dependency management tool? A dependency management tool is a software system or utility that automates the process of identifying, retrieving, updating, and maintaining the external libraries or packages (referred to as dependencies) required by a software project. It ensures that all necessary dependencies are included and managed in a standardised way, which helps prevent version conflicts, missing libraries, and manual errors during software development.</description><content:encoded>&lt;![CDATA[<h2 id="what-is-a-dependency-management-tool">What is a dependency management tool?</h2><p>A<strong>dependency management tool</strong> is a software system or utility that automates the process of identifying, retrieving, updating, and maintaining the external libraries or packages (referred to as<strong>dependencies</strong>) required by a software project. It ensures that all necessary dependencies are included and managed in a standardised way, which helps prevent version conflicts, missing libraries, and manual errors during software development.</p><ol><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#what-is-a-dependency-management-tool">What is a dependency management tool?</a><ol><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#dependency-identification">Dependency Identification:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#fetching-dependencies">Fetching Dependencies:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#version-management">Version Management:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#scope-environment-management">Scope &amp; Environment Management:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#build-process-integration">Build Process Integration:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#popular-dependency-management-tools">Popular Dependency Management Tools:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#benefits-of-dependency-management-tools">Benefits of Dependency Management Tools:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#example-workflow">Example Workflow:</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#the-classical-dependency-manager-for-java-maven">The classical Dependency manager for Java - Maven</a><ol><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#project-structure-and-pom-xml">Project Structure and<code>pom.xml</code></a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#build-lifecycle">Build Lifecycle</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#dependency-management">Dependency Management</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#maven-repositories">Maven Repositories</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#plugins">Plugins</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#how-it-all-works-together">How It All Works Together:</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#bld-the-lightweight-java-build-system">BLD - the lightweight Java Build System</a><ol><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#key-features-of-bld">Key Features of bld</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#how-bld-stands-out-in-the-java-ecosystem">How bld Stands Out in the Java Ecosystem</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#usage-example">Usage Example</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#the-difference-between-maven-and-bld">The difference between Maven and BLD</a><ol><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#build-language">Build Language:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#declarative-vs-code-based">Declarative vs. Code-Based:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#task-execution">Task Execution:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#ide-integration">IDE Integration:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#dependency-management">Dependency Management:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#build-artifacts">Build Artifacts:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#complexity-and-learning-curve">Complexity and Learning Curve:</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#performance">Performance:</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#how-to-start-with-bld">How to start with bld?</a></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#an-example-migration-of-a-project-from-maven-to-bld">An Example migration of a project from Maven to bld</a><ol><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#the-maven-project-structure">The Maven Project Structure</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/26/bld-a-lightweight-java-build-tool/#conclusion">Conclusion</a></li></ol><div class="pull-quote"><p>Demo - Project are available here:<a href="https://github.com/Java-Publications/Blog---Core-Java---2024.08---bld-the-lightweight-build-tool">https://github.com/Java-Publications/Blog---Core-Java---2024.08---bld-the-lightweight-build-tool</a></p></div><p>Here’s how dependency management tools generally function:</p><h3 id="dependency-identification">Dependency Identification:</h3><p>Developers list the libraries or frameworks their project depends on in a configuration file (like Maven’s pom.xml or Gradle’s build.gradle). Each dependency is defined by attributes such as<strong>group ID</strong> ,<strong>artifact ID</strong> , and<strong>version</strong>.</p><p><strong>Example (Maven)</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;groupId&gt;</span>junit<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;artifactId&gt;</span>junit<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;version&gt;</span>4.12<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><h3 id="fetching-dependencies">Fetching Dependencies:</h3><p>The tool retrieves the required dependencies from<strong>repositories</strong> (such as Maven Central or custom/private repositories) and downloads them to a<strong>local repository</strong> on the developer’s machine. These repositories host many external libraries, frameworks, and plugins.</p><p><strong>Local Repository</strong> : A cache stored locally on a developer’s machine to prevent repeated downloads.</p><p><strong>Remote Repository</strong> : Centralized or custom servers hosted by the dependencies (e.g., Maven Central).</p><h3 id="version-management">Version Management:</h3><p>Dependency management tools handle<strong>dependency versioning</strong> , ensuring the correct versions of external libraries are used. Tools like Maven or Gradle support<strong>transitive dependency resolution</strong> , meaning if a dependency relies on another library, that secondary library will also be fetched automatically.</p><p><strong>Version Conflicts</strong> : If two libraries depend on different versions of the same dependency, the tool resolves the conflict by selecting the nearest version in the dependency tree or the explicitly defined one.</p><h3 id="scope--environment-management">Scope &amp; Environment Management:</h3><p>Dependency management tools allow for different<strong>scopes</strong> or<strong>profiles</strong> that specify when a dependency is required (e.g., only during testing or in a production environment). This helps optimise resource usage and prevent unnecessary dependencies from being included in the final build.</p><p><strong>Example Scopes</strong> :</p><p><strong>Compile</strong> : Dependencies available during compilation and runtime.</p><p><strong>Test</strong> : Dependencies used only during testing (e.g., JUnit).</p><p><strong>Provided</strong> : Dependencies expected to be supplied by the runtime environment.</p><h3 id="build-process-integration">Build Process Integration:</h3><p>Dependency management tools integrate into a project&rsquo;s build lifecycle. They ensure that the right versions of libraries are fetched before compiling, packaging, or testing the application. For instance, Maven&rsquo;s<code>**install**</code> phase ensures that all dependencies are installed in the local repository before building.</p><h3 id="popular-dependency-management-tools">Popular Dependency Management Tools:</h3><p><strong>Maven</strong> : Uses<code>**pom.xml**</code> to define dependencies and follows a declarative model with predefined lifecycles.</p><p><strong>Gradle</strong> : Uses Groovy or Kotlin DSL for its build scripts, offering more flexibility and allowing for custom build logic.</p><p><strong>npm (Node Package Manager)</strong> : Manages dependencies for JavaScript and Node.js projects, using a<code>**package.json**</code> file.</p><p><strong>bld</strong> : A newer tool for Java projects that allows developers to define dependencies directly in Java code.</p><h3 id="benefits-of-dependency-management-tools">Benefits of Dependency Management Tools:</h3><p><strong>Automation</strong> : They automate the retrieval and management of libraries, removing the need for developers to download and include them manually.</p><p><strong>Consistency</strong> : Ensures that every developer in a team uses identical versions of dependencies, leading to more consistent builds.</p><p><strong>Conflict Resolution</strong> : Handles version conflicts between dependencies automatically, preventing runtime errors.</p><p><strong>Efficiency</strong> : By caching dependencies locally, tools reduce repeated downloads and improve build times.</p><p><strong>Security</strong> : Modern tools often check for security vulnerabilities in dependencies and alert developers to updates.</p><h3 id="example-workflow">Example Workflow:</h3><p>A developer declares dependencies in a configuration file.</p><p>The tool checks the local repository for cached versions of the libraries.</p><p>If dependencies aren’t cached, the tool fetches them from a remote repository.</p><p>The tool ensures all dependencies are included during the build process (compile, test, package, etc.).</p><p>Dependency management tools are vital for modern software development, especially in large projects with numerous external libraries. They automate the complex process of handling dependencies, reducing manual errors, improving efficiency, and ensuring the project builds correctly across different environments.</p><h2 id="the-classical-dependency-manager-for-java---maven">The classical Dependency manager for Java - Maven</h2><p>Maven is a build automation and project management tool primarily used in Java projects. It uses a<strong>declarative model</strong> to manage a project’s build, reporting, and dependencies using an XML configuration file called<code>**pom.xml**</code>. Below is an overview of how Maven works:</p><h3 id="project-structure-and-pomxml">Project Structure and<code>pom.xml</code></h3><p>At the heart of every Maven project is the<code>**pom.xml**</code> file (Project Object Model). This file defines the project structure, dependencies, build plugins, and goals. Here&rsquo;s what<code>**pom.xml**</code> generally contains:</p><p><strong>Group ID</strong> : Identifies the project’s group (often based on package naming).</p><p><strong>Artifact ID</strong> : A unique identifier for the project.</p><p><strong>Version</strong> : Version of the project being built.</p><p><strong>Dependencies</strong> : A list of libraries or frameworks the project requires (such as<code>**JUnit**</code> for testing or<code>**Spring**</code> for dependency injection).</p><p><strong>Plugins</strong> : Additional tools that enhance the build process (for tasks like creating JAR files, running tests, etc.).</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;project&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;modelVersion&gt;</span>4.0.0<span class="nt">&lt;/modelVersion&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;groupId&gt;</span>com.example<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;artifactId&gt;</span>my-app<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;version&gt;</span>1.0-SNAPSHOT<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;dependencies&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;groupId&gt;</span>junit<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;artifactId&gt;</span>junit<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;version&gt;</span>4.12<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;/dependency&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;/dependencies&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;/project&gt;</span></span></span></code></pre></div></div><h3 id="build-lifecycle">Build Lifecycle</h3><p>Maven operates on a series of<strong>lifecycles</strong>. The<strong>default lifecycle</strong> consists of a sequence of build phases that are invoked when building a project:</p><ul><li><strong>validate</strong> : Check if the project is correct and all necessary information is available.</li><li><strong>compile</strong> : Compile the source code of the project.</li><li><strong>test</strong> : Run tests on the compiled code using a suitable testing framework (e.g., JUnit).</li><li><strong>package</strong> : Package the compiled code into a distributable format, like a JAR or WAR.</li><li><strong>install</strong> : Install the package into the local repository for use as a dependency in other local projects.</li><li><strong>deploy</strong> : Copy the final package to the remote repository for sharing with other developers.</li></ul><p>These lifecycle phases are sequential, meaning invoking<code>**install**</code> will also execute all phases before it (from<code>**validate**</code> to<code>**package**</code>).</p><h3 id="dependency-management">Dependency Management</h3><p>Maven’s most powerful feature is its<strong>dependency management</strong> system. Maven fetches external libraries from<strong>Maven Central</strong> , a global repository that hosts many open-source libraries. Maven uses<strong>transitive dependency resolution</strong> , meaning that if Project A depends on Library B and Library B depends on Library C, Maven will automatically fetch Library C.</p><p>Dependencies are defined in the<code>**pom.xml**</code> under the<code>**&lt; dependencies&gt;**</code> section, as shown above. Maven manages and can automatically update versions, making integrating libraries very efficient.</p><h3 id="maven-repositories">Maven Repositories</h3><p>Maven uses three types of repositories:</p><p><strong>Local Repository</strong> : This is on the developer’s machine. When Maven builds a project, it checks the local repository first to resolve dependencies.</p><p><strong>Central Repository</strong> : Maven Central is the default remote repository where Maven looks for dependencies that are not available in the local repository.</p><p><strong>Remote Repository</strong> : Additional remote repositories, such as private or custom repositories that host specific artefacts, can be defined.</p><h3 id="plugins">Plugins</h3><p>Plugins in Maven extend its functionality. Some common Maven plugins include:</p><p><strong>maven-compiler-plugin</strong> : Compiles the source code.</p><p><strong>maven-surefire-plugin</strong> : Runs unit tests.</p><p><strong>maven-jar-plugin</strong> : Packages the project as a JAR file.</p><p><strong>maven-war-plugin</strong> : Packages the project as a WAR file for web applications.</p><p>These plugins are usually added to the<code>**pom.xml**</code> under the<code>**&lt; build&gt;**</code> section.</p><p><strong>Profiles</strong></p><p>Maven also supports profiles, which allow developers to define different configurations for different environments (e.g., development, testing, production). Profiles are specified in the<strong>pom.xm</strong> l and can be activated by different triggers, such as system properties or environments.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;profiles&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;profile&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;id&gt;</span>dev<span class="nt">&lt;/id&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;properties&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;env&gt;</span>development<span class="nt">&lt;/env&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;/properties&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;/profile&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;/profiles&gt;</span></span></span></code></pre></div></div><p><strong>Multi-Module Projects</strong></p><p>Maven can handle<strong>multi-module projects</strong> , where a single project is divided into multiple sub-projects or modules. Each module typically has its own<code>**pom.xml**</code> but is coordinated by a parent POM. This feature is helpful for large projects with independent yet related components.</p><h3 id="how-it-all-works-together">How It All Works Together:</h3><p>1. The developer defines the project structure and dependencies in the<code>pom.xml</code>.</p><p>2. Maven processes this file and resolves all dependencies by downloading required JAR files from remote repositories (if they are not found in the local repository).</p><p>3. Maven executes phases from the build lifecycle, compiling, testing, and packaging the project.</p><p>4. Plugins add extra functionality, such as running tests, generating documentation, and creating artefacts.</p><p>Maven’s declarative model and lifecycle management make it a popular choice for managing complex Java projects. Its rich plugin ecosystem and dependency management reduce manual effort while standardising the build process. However, the XML-heavy configuration can be cumbersome for some developers, especially compared to newer build tools like<strong>bld</strong>.</p><h2 id="bld---the-lightweight-java-build-system">BLD - the lightweight Java Build System</h2><p><strong>bld</strong> is a lightweight build tool designed specifically for the Java ecosystem. Unlike traditional build tools like Maven or Gradle, which rely on declarative syntax and often involve complex configurations,<strong>bld</strong> allows developers to write their build logic in pure Java. This approach simplifies the learning curve and reduces the cognitive load on developers by enabling them to stay within the Java language for both application and build logic, making it a particularly modern and streamlined tool for Java projects.</p><h3 id="key-features-of-bld">Key Features of bld</h3><p><strong>Java-Based Build Logic</strong> : The most distinctive aspect of<strong>bld</strong> is that it uses Java itself to define the build process. This eliminates the need for learning domain-specific languages (DSLs) or XML configurations as is the case with Maven or Gradle. With<strong>bld</strong> , build logic becomes part of the Java code, making it easy to reason about and maintain in the same environment where application development occurs.</p><p><strong>Explicit Task Execution</strong> :<strong>bld</strong> emphasises a transparent and predictable execution model. Tasks in<strong>bld</strong> do not run automatically but must be explicitly triggered, giving developers complete control over the build process. This removes any &ldquo;auto-magical&rdquo; behaviour often associated with other tools, which can sometimes lead to unexpected outcomes.</p><p><strong>Simple Dependency Management</strong> : Dependency management in<strong>bld</strong> is intuitive and leverages Java constructs. Dependencies are managed through a standard library system, and they can be added directly in the Java build script by specifying the Maven coordinates. This system allows<strong>bld</strong> to handle fetching and resolving dependencies automatically, similar to Maven or Gradle, but with less complexity.</p><p><strong>Integration with Java 17</strong> :<strong>bld</strong> takes full advantage of the latest Java features, relying on Java 17 as its minimum requirement. This allows developers to use modern Java syntax, including records, sealed classes, and pattern matching, to write concise and clean build scripts. The tool supports features like code auto-completion and Javadoc integration, making it easy to navigate within popular Integrated Development Environments (IDEs).</p><p><strong>IDE Support</strong> :<strong>bld</strong> has integrations for popular IDEs like IntelliJ IDEA, offering features such as automatic project detection, quick access to the main build class, and the ability to run multiple build commands directly from the IDE. This smooths the workflow for developers who prefer using graphical interfaces over command-line interactions.</p><p><strong>Command-Line Interface (CLI)</strong> : The<strong>bld</strong> CLI offers a set of standard commands that allow developers to compile, run, test, and package their projects. It supports everyday build tasks like creating JAR files, managing dependencies, running tests, and generating Javadoc. Developers can also define custom commands within their build scripts using Java, extending the tool’s flexibility.</p><h3 id="how-bld-stands-out-in-the-java-ecosystem">How bld Stands Out in the Java Ecosystem</h3><p>In the Java build ecosystem, tools typically fall into two categories: declarative (like Maven) and code-based (like Gradle).<strong>bld</strong> is part of the second group but adds the advantage of being immediately executable without predefining build plans. This immediate execution reduces the complexity of managing and understanding the build pipeline.</p><p>For developers familiar with scripting environments like Node.js or Go,<strong>bld</strong> &rsquo;s ergonomics offer a familiar, streamlined experience. It delivers the ease of dependency management while maintaining transparency and direct control, helping to avoid common issues associated with over-engineered build systems.</p><h3 id="usage-example">Usage Example</h3><p>A basic<strong>bld</strong> file for a Java project could look like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.example</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">rife.bld.Project</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.List</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">rife.bld.dependencies.Repository.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">rife.bld.dependencies.Scope.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MyappBuild</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">Project</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">MyappBuild</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">pkg</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"com.example"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Myapp"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">mainClass</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"com.example.MyappMain"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">version</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">version</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">repositories</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">List</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="n">MAVEN_CENTRAL</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">scope</span><span class="p">(</span><span class="n">compile</span><span class="p">).</span><span class="na">include</span><span class="p">(</span><span class="n">dependency</span><span class="p">(</span><span class="s">"com.fasterxml.jackson.core"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"jackson-databind"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">version</span><span class="p">(</span><span class="n">2</span><span class="p">,</span><span class="w"/><span class="n">16</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">)));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this setup, a developer specifies dependencies in the build file itself, alongside build configurations, removing the need for separate files like<code>**pom.xml**</code> in Maven.</p><p><strong>bld</strong> offers a powerful yet simple alternative for Java developers who seek to avoid the complexities of traditional build tools. Writing build logic in Java, using an explicit task execution model, and supporting modern Java features provide a clear and concise way to manage builds, dependencies, and distribution. It suits developers who prefer a code-centric approach to their entire development lifecycle, from coding to building.</p><h2 id="the-difference-between-maven-and-bld">The difference between Maven and BLD</h2><p><strong>bld</strong> and<strong>Maven</strong> are both Java build tools, but they differ significantly in their approach, complexity, and usage. Here’s a comparison of the two:</p><h3 id="build-language">Build Language:</h3><p><strong>Maven</strong> : Uses an XML-based configuration file called<code>**pom.xml**</code> (Project Object Model). This declarative approach requires developers to define a static configuration of dependencies, plugins, and tasks in a verbose XML format.</p><p><strong>bld</strong> : It uses<strong>Java</strong> as the build scripting language, meaning developers write their build logic using Java code. This approach leverages Java&rsquo;s advantages, such as type safety, auto-completion, and the ability to refactor using IDE tools.</p><h3 id="declarative-vs-code-based">Declarative vs. Code-Based:</h3><p><strong>Maven</strong> : Fully declarative. Developers declare what they want to happen, and Maven uses its lifecycle to determine how to execute tasks. This provides predictability but can be hard to customise beyond its standard behaviour.</p><p><strong>bld</strong> : Primarily<strong>code-based</strong>. Developers write Java code that is executed immediately. This provides more flexibility and control over the build process, allowing custom build flows and logic to be easily implemented.</p><h3 id="task-execution">Task Execution:</h3><p><strong>Maven</strong> : Its lifecycle uses predefined phases, such as<code>**validate**</code>,<code>**compile**</code>,<code>**test**</code>,<code>**package**</code>, and<code>**install**</code>. Each phase automatically triggers a set of goals, which is convenient for standard projects but can become cumbersome if you need custom behaviour outside of Maven’s predefined lifecycle.</p><p><strong>bld</strong> : Offers<strong>explicit control</strong> over tasks. Tasks are not automatically linked in a lifecycle; developers must call them explicitly, which provides more control and avoids the hidden behaviour common in Maven.</p><h3 id="ide-integration">IDE Integration:</h3><p><strong>Maven</strong> : Has widespread support across IDEs like Eclipse, IntelliJ IDEA, and NetBeans. It has been around for a long time, so IDEs can automatically recognise Maven projects and provide support like dependency management, project configuration, and running lifecycle phases directly from the IDE.</p><p><strong>bld</strong> : Offers integration with<strong>IntelliJ IDEA</strong> and leverages the build logic as Java code, providing features such as code navigation, auto-completion, and even IDE-run configurations.</p><h3 id="dependency-management-1">Dependency Management:</h3><p><strong>Maven</strong> : Dependency management is one of Maven’s most vital features. It uses a central repository (Maven Central) and follows a transitive dependency model, automatically downloading dependencies and their sub-dependencies. However, this sometimes results in dependency conflicts or versioning issues.</p><p><strong>bld</strong> : Similar to Maven,<strong>bld</strong> supports dependency management using Maven Central or other repositories. However, it allows developers to handle dependency logic within the Java build file itself, which can simplify certain aspects of dependency resolution.</p><h3 id="build-artifacts">Build Artifacts:</h3><p><strong>Maven</strong> : Offers a standardised way to build JAR, WAR, and other packages using plugins. It also supports additional artefact types like sources, documentation, and testing.</p><p><strong>bld</strong> : Supports similar build artifacts (JAR, UberJAR, etc.) but gives developers more direct control over how these are generated. For example, developers can include custom commands to create specific outputs like precompiled templates.</p><h3 id="complexity-and-learning-curve">Complexity and Learning Curve:</h3><p><strong>Maven</strong> : Can be complex, especially when dealing with multi-module projects or needing custom behaviour that requires a deep understanding of Maven’s plugin architecture and lifecycle phases. Modifying or extending Maven’s behaviour often involves writing custom plugins or extensive XML configuration.</p><p><strong>bld</strong> : Aims to reduce complexity by allowing developers to manage builds using familiar Java code. It is simpler for Java developers to grasp, and writing custom build logic feels more natural compared to learning Maven&rsquo;s XML configuration and plugins.</p><h3 id="performance">Performance:</h3><p><strong>Maven</strong> : Due to its lifecycle model and plugin architecture, Maven can be slower, mainly when performing complete builds with many plugins or complex dependency graphs. Incremental builds and caching are not as optimised as some newer tools.</p><p><strong>bld</strong> : Provides<strong>faster builds</strong> in many cases due to its simpler execution model and the immediate execution of Java code without going through multiple lifecycle phases.</p><h2 id="how-to-start-with-bld">How to start with bld?</h2><p>The easiest way to start with bld is on commandline. For this go into the directory where you want to start with the project. Open a terminal and execute the following command that is available at the home page of the project:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="n">bash</span><span class="w"/><span class="o">-</span><span class="k">c</span><span class="w"/><span class="s2">"$(curl -fsSL https://rife2.com/bld/create.sh)"</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Downloading</span><span class="w"/><span class="n">bld</span><span class="w"/><span class="n">v2</span><span class="p">.</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Welcome</span><span class="w"/><span class="k">to</span><span class="w"/><span class="n">bld</span><span class="w"/><span class="n">v2</span><span class="p">.</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="p">.</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Please</span><span class="w"/><span class="n">enter</span><span class="w"/><span class="n">a</span><span class="w"/><span class="nb">number</span><span class="w"/><span class="k">for</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">project</span><span class="w"/><span class="k">type</span><span class="p">:</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="mi">1</span><span class="p">:</span><span class="w"/><span class="n">base</span><span class="w">  </span><span class="p">(</span><span class="n">Java</span><span class="w"/><span class="n">baseline</span><span class="w"/><span class="n">project</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="mi">2</span><span class="p">:</span><span class="w"/><span class="n">app</span><span class="w">   </span><span class="p">(</span><span class="n">Java</span><span class="w"/><span class="n">application</span><span class="w"/><span class="n">project</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="mi">3</span><span class="p">:</span><span class="w"/><span class="n">lib</span><span class="w">   </span><span class="p">(</span><span class="n">Java</span><span class="w"/><span class="n">library</span><span class="w"/><span class="n">project</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="mi">4</span><span class="p">:</span><span class="w"/><span class="n">rife2</span><span class="w"> </span><span class="p">(</span><span class="n">RIFE2</span><span class="w"/><span class="n">web</span><span class="w"/><span class="n">application</span><span class="p">)</span></span></span></code></pre></div></div><p>This will download the bld-Script and execute it. Now, you can choose what kind of project you want to start. I am selecting<strong>1</strong> for a baseline project. The next questions are package name and application name. With this informationes the generating process will be done. You will find a directory with the appname inside the directory. In my example it is the name<strong>core</strong>.</p><p>Inside the app directory there is a script for unix/osx and one for windows. This is the wrapper for bld. I am using the osx version in my example here.</p><p>To test if the installation is correct, use the command<strong>./bld version</strong>. In my case the answer is 2.6.0.</p><p>There are two directories. The first one is called<strong>lib</strong>. Inside there are different directories, one is called<strong>bld</strong> and the others are named by the different scopes you know from the maven lifecycles. (<strong>compile</strong> ,<strong>provided</strong> ,<strong>runtime</strong> and<strong>test</strong>). Later you will find the dependencies that are required for the different scopes inside these directories. The most important directory right now, is the directory called<strong>bld</strong>. Inside the directory there is the build related files like the wrapper, properties and cache. For customizing the build environment, the properties file is the right place to go. Here you can activate extensions for example. But this will be dome later.</p><p>Back at the root directory of the project, there is the<strong>src</strong> folder as well. Inside the source folder are three subfolders. The src and test folder is known from maven already. The third folder called<strong>bld</strong> contains the sources for the build configuration. The Class is called appname plus “Build” in this case it is<strong>CoreBuild</strong>.</p><p>Here we can start defining the build process itself.</p><h2 id="an-example-migration-of-a-project-from-maven-to-bld">An Example migration of a project from Maven to bld</h2><p>Migrating a Maven project to<strong>bld</strong> involves several steps, as<strong>bld</strong> operates on Java-based build scripts instead of Maven’s<code>**pom.xml**</code> XML-based configuration. Here&rsquo;s a general example of how you might approach migrating a Maven project to<strong>bld</strong>.</p><h3 id="the-maven-project-structure">The Maven Project Structure</h3><p>Before migrating, you need to analyse the Maven project structure:</p><p><code>**pom.xml**</code>: This file contains dependencies, plugins, repositories, build profiles, and other configurations.</p><p>We will go through different sections of the pom.xml and transforming them into the bld version.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;groupId&gt;</span>com.svenruppert<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="nt">&lt;artifactId&gt;</span>core<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="nt">&lt;version&gt;</span>0.0.1-SNAPSHOT<span class="nt">&lt;/version&gt;</span></span></span></code></pre></div></div><p>Start by creating a new<strong>bld</strong> build script in Java.<strong>bld</strong> uses pure Java code to define the build logic, dependencies, and configurations, allowing you to migrate the settings from the<code>**pom.xml**</code>. First, create a<code>**Build.java**</code> file (or similar) in your<code>**src/bld/java/**</code> directory, if not already existing. Here is a starting point for the migration:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">package</span><span class="w"/><span class="nn">com.svenruppert</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">rife.bld.Project</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">rife.bld.dependencies.VersionNumber</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Path</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.List</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">CoreBuild</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">Project</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="nf">CoreBuild</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">pkg</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"com.svenruppert"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Core"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">mainClass</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"com.svenruppert.Application"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">version</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">version</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="s">"SNAPSHOT"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="k">new</span><span class="w"/><span class="n">CoreBuild</span><span class="p">().</span><span class="na">start</span><span class="p">(</span><span class="n">args</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>As next we are migrating the definition of the repositories.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;repositories&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;snapshots&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;enabled&gt;</span>false<span class="nt">&lt;/enabled&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;/snapshots&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;id&gt;</span>central<span class="nt">&lt;/id&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;name&gt;</span>libs-release<span class="nt">&lt;/name&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;url&gt;</span>https://repo.maven.apache.org/maven2/<span class="nt">&lt;/url&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;repository&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;snapshots&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;enabled&gt;</span>true<span class="nt">&lt;/enabled&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;updatePolicy&gt;</span>always<span class="nt">&lt;/updatePolicy&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;/snapshots&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;id&gt;</span>snapshots<span class="nt">&lt;/id&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;name&gt;</span>libs-snapshot<span class="nt">&lt;/name&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;url&gt;</span>https://repo.maven.apache.org/maven2/<span class="nt">&lt;/url&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;/repository&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/repositories&gt;</span></span></span></code></pre></div></div><p>This will lead to a statement like the following inside the Build-Class.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">repositories</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">List</span><span class="p">.</span><span class="na">_of_</span><span class="p">(</span><span class="n">_MAVEN_CENTRAL_</span><span class="w"/><span class="p">,</span><span class="w"/><span class="n">_RIFE2_RELEASES_</span><span class="p">);</span></span></span></code></pre></div></div><p>For the most projects you will need some dependencies in different scopes. The xml - Version wil look like the following. I am not listing all the dependencies of the demo project.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;groupId&gt;</span>io.javalin<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;artifactId&gt;</span>javalin<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;version&gt;</span>6.3.0<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;groupId&gt;</span>io.javalin<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;artifactId&gt;</span>javalin-testtools<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;version&gt;</span>6.3.0<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><p>The bld Version:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">VersionNumber</span><span class="w"/><span class="n">versionJavalin</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">version</span><span class="p">(</span><span class="n">6</span><span class="p">,</span><span class="w"/><span class="n">3</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">scope</span><span class="p">(</span><span class="n">compile</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">include</span><span class="p">(</span><span class="n">dependency</span><span class="p">(</span><span class="s">"io.javalin"</span><span class="p">,</span><span class="w"/><span class="s">"javalin"</span><span class="p">,</span><span class="w"/><span class="n">versionJavalin</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">scope</span><span class="p">(</span><span class="n">test</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">include</span><span class="p">(</span><span class="n">dependency</span><span class="p">(</span><span class="s">"io.javalin"</span><span class="p">,</span><span class="w"/><span class="s">"javalin-testtools"</span><span class="p">,</span><span class="w"/><span class="n">versionJavalin</span><span class="p">));</span></span></span></code></pre></div></div><p>Finally we a have to migrate the plugins from maven to bld. If your Maven project used custom lifecycle phases, you can create<strong>custom commands</strong> in<strong>bld</strong> using Java methods annotated with<code>**@BuildCommand**</code>. This allows you to replicate any special build steps that were part of your Maven process, like packaging, publishing, or precompilation tasks.</p><p>Example of adding a custom command in<strong>bld</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@BuildCommand</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">customTask</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Running custom task..."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here we are at a point, where we can see, that bld is not as feature ritch as maven out of the box. But, if you need support for another plugin or lifecycle, have a look at the documentation about extensions. Here you will find all you need to create one by yourself. The cool thing here is, that everything can be done on core java. Even complex tasks can be implemented including all technologies that are needed for this step.</p><p>But, back to the migration. In my case I am using the<strong>pitest</strong> plugin to generate the mutation test coverage reports.</p><p>Inside the<strong>pom.xml</strong> you have to declare the following:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;plugin&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;groupId&gt;</span>org.pitest<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;artifactId&gt;</span>pitest-maven<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;version&gt;</span>1.14.1<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;dependencies&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;groupId&gt;</span>org.pitest<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;artifactId&gt;</span>pitest-junit5-plugin<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;version&gt;</span>1.2.1<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;/dependency&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;/dependencies&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;configuration&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;threads&gt;</span>2<span class="nt">&lt;/threads&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;outputFormats&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;outputFormat&gt;</span>XML<span class="nt">&lt;/outputFormat&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;outputFormat&gt;</span>HTML<span class="nt">&lt;/outputFormat&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;/outputFormats&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;targetClasses&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;param&gt;</span>${pitest-prod-classes}<span class="nt">&lt;/param&gt;</span></span></span><span class="line"><span class="cl">     <span class="c">&lt;!--&lt;param&gt;org.reflections.*&lt;/param&gt;--&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;/targetClasses&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;targetTests&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;param&gt;</span>${pitest-test-classes}<span class="nt">&lt;/param&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;/targetTests&gt;</span></span></span><span class="line"><span class="cl"> <span class="nt">&lt;/configuration&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/plugin&gt;</span></span></span></code></pre></div></div><p>The corresponding<strong>bld</strong> part will be:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">scope</span><span class="p">(</span><span class="n">test</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">include</span><span class="p">(</span><span class="n">dependency</span><span class="p">(</span><span class="s">"org.pitest"</span><span class="p">,</span><span class="w"/><span class="s">"pitest"</span><span class="p">,</span><span class="w"/><span class="n">version</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">17</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">)))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">include</span><span class="p">(</span><span class="n">dependency</span><span class="p">(</span><span class="s">"org.pitest"</span><span class="p">,</span><span class="w"/><span class="s">"pitest-command-line"</span><span class="p">,</span><span class="w"/><span class="n">version</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">17</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">)))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">include</span><span class="p">(</span><span class="n">dependency</span><span class="p">(</span><span class="s">"org.pitest"</span><span class="p">,</span><span class="w"/><span class="s">"pitest-junit5-plugin"</span><span class="p">,</span><span class="w"/><span class="n">version</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">2</span><span class="p">,</span><span class="w"/><span class="n">1</span><span class="p">)))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">.</span><span class="na">include</span><span class="p">(</span><span class="n">dependency</span><span class="p">(</span><span class="s">"org.pitest"</span><span class="p">,</span><span class="w"/><span class="s">"pitest-testng-plugin"</span><span class="p">,</span><span class="w"/><span class="n">version</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">)));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@BuildCommand</span><span class="p">(</span><span class="n">summary</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Run PIT mutation tests"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">pit</span><span class="p">()</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="k">new</span><span class="w"/><span class="n">PitestOperation</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">fromProject</span><span class="p">(</span><span class="k">this</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">reportDir</span><span class="p">(</span><span class="n">Path</span><span class="p">.</span><span class="na">of</span><span class="p">(</span><span class="s">"reports"</span><span class="p">,</span><span class="w"/><span class="s">"mutations"</span><span class="p">).</span><span class="na">toString</span><span class="p">())</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">targetClasses</span><span class="p">(</span><span class="n">pkg</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">".*"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">targetTests</span><span class="p">(</span><span class="s">"junit."</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">pkg</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">".*"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">verbose</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">.</span><span class="na">execute</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Now, you are able to trigger the pitest extension on commandline with<strong>./bld pitest.</strong> To activate the plugin there must be the declaration inside the file “<strong>lib/bld/bld-wrapper.properties</strong> ”. The value of this key is the plugin maven coordinates including the version number.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">properties</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-properties" data-lang="properties"><span class="line"><span class="cl"><span class="na">bld.extensions</span><span class="o">=</span><span class="s">com.uwyn.rife2:bld-pitest:1.0.2</span></span></span></code></pre></div></div><p>With this we are able to migrate maven project into bld-projects.</p><h2 id="conclusion">Conclusion</h2><p>Migrating a complex Maven project to<strong>bld</strong> involves translating the dependency management, build logic, and testing configurations from<code>pom.xml</code> into Java code using<strong>bld</strong> ’s project class structure. While both tools manage dependencies and build processes,<strong>bld</strong> allows developers to write build logic in Java, giving more flexibility and making it easier to manage as part of the application codebase.</p><p>With<strong>bld</strong> , you gain direct control over the build process through Java code, but you must ensure that all elements of your Maven build (dependencies, plugins, phases) are correctly transferred.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Tools</category><category>Uncategorized</category><media:content url="https://svenruppert.com/images/2024/09/IMG_0530.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/09/IMG_0530.jpg"/><enclosure url="https://svenruppert.com/images/2024/09/IMG_0530.jpg" type="image/jpeg" length="0"/></item><item><title>What are component-based web application frameworks for Java Developers?</title><link>https://svenruppert.com/posts/what-are-component-based-web-application-frameworks-for-java-developers/</link><pubDate>Wed, 25 Sep 2024 17:04:18 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/what-are-component-based-web-application-frameworks-for-java-developers/</guid><description>Tapestry, Wicket, and Vaadin A component-oriented Java web application framework is a development framework that enables the construction of web applications in a modular way, using reusable, encapsulated components that manage their state, behaviour, and presentation. This approach allows developers to build complex user interfaces by assembling pre-built or custom components, like building blocks, each handling specific functionalities within the application.</description><content:encoded>&lt;![CDATA[<h1 id="tapestry-wicket-and-vaadin">Tapestry, Wicket, and Vaadin</h1><p>A component-oriented Java web application framework is a development framework that enables the construction of web applications in a modular way, using reusable, encapsulated components that manage their state, behaviour, and presentation. This approach allows developers to build complex user interfaces by assembling pre-built or custom components, like building blocks, each handling specific functionalities within the application.</p><ol><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#tapestry-wicket-and-vaadin">Tapestry, Wicket, and Vaadin</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#what-is-the-challenge-in-creating-component-oriented-web-frameworks-in-java">What is the challenge in creating component-oriented web frameworks in Java?</a><ol><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#state-management">State Management</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#component-lifecycle">Component Lifecycle</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#event-handling">Event Handling</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#performance-optimisation">Performance Optimisation</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#seamless-integration-with-client-side-technologies">Seamless Integration with Client-Side Technologies</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#security-concerns">Security Concerns</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#usability-and-developer-productivity">Usability and Developer Productivity</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#scalability">Scalability</a></li></ol></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#what-is-apache-tapestry">What is Apache Tapestry?</a><ol><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#use-cases">Use Cases</a></li></ol></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#what-is-apache-wicket">What is Apache Wicket?</a><ol><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#key-features-of-apache-wicket">Key Features of Apache Wicket</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#use-cases">Use Cases</a></li></ol></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#vaadin-plattform">Vaadin Plattform</a><ol><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#vaadin-flow">Vaadin Flow</a></li></ol></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#the-difference-between-apache-tapestry-apache-wicket-and-vaadin-flow">The Difference between Apache Tapestry, Apache Wicket and Vaadin Flow</a><ol><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#apache-tapestry">Apache Tapestry</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#apache-wicket">Apache Wicket</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#vaadin-flow">Vaadin Flow</a></li><li><a href="https://svenruppert.com/2024/04/19/what-are-component-based-web-application-frameworks-for-java-developers/#key-differentiators">Key Differentiators</a></li></ol></li></ol><p><strong>Encapsulation</strong> : Each component is a self-contained unit that includes its own logic and data handling. This encapsulation helps hide the internal details of components while exposing a defined interface with which other components can interact.</p><p><strong>Reusability</strong> : Components are designed to be reused across different parts of an application or even across multiple projects. This can significantly speed up development and ensure consistency throughout the application.</p><p><strong>Modularity</strong> : The application can be divided into smaller, manageable parts (components), which can be developed, tested, and maintained independently. This modularity makes it easier to manage complex applications and allows multiple developers to work on different components simultaneously without conflicts.</p><p><strong>Interactivity</strong> : Component-oriented frameworks often support event-driven programming, allowing components to react to user inputs or other system events. This makes it straightforward to build dynamic and interactive web applications.</p><p><strong>Integration</strong> : These frameworks typically provide easy integration with other technologies, such as databases, web services, and third-party libraries, allowing components to interact with these resources smoothly.</p><h1 id="what-is-the-challenge-in-creating-component-oriented-web-frameworks-in-java">What is the challenge in creating component-oriented web frameworks in Java?</h1><p>Creating component-oriented web frameworks in Java poses several challenges, primarily due to the complexities of web development and the nature of Java as a server-side technology. Here are some of the main challenges:</p><h2 id="state-management">State Management</h2><p>Web applications are inherently stateless, meaning each request is independent of previous requests. Component-oriented frameworks need to manage the state across multiple requests and user sessions. This can be complex, particularly when considering the need to synchronise the state between the server and the client while ensuring performance and scalability.</p><h2 id="component-lifecycle">Component Lifecycle</h2><p>Managing the lifecycle of components—creation, initialisation, rendering, updates, and destruction—is a significant challenge. Each component must handle its lifecycle efficiently, interacting with other components and the broader application environment without memory leaks or performance bottlenecks.</p><h2 id="event-handling">Event Handling</h2><p>In a typical desktop application, the event-driven model is straightforward because the application runs in a single environment. However, in web applications, the user interface runs in the browser (client-side), while the business logic often runs on the server. This separation requires a robust mechanism to handle events on the client side, process them on the server, and reflect the changes to the client, all without making the application feel sluggish to the user.</p><h2 id="performance-optimisation">Performance Optimisation</h2><p>Component-oriented frameworks can introduce overhead due to the need for fine-grained components to manage their state and behaviour. This can impact performance, as every user interaction might require server communication. Handling this communication and minimising the data transferred between client and server is crucial for maintaining a responsive user experience.</p><h2 id="seamless-integration-with-client-side-technologies">Seamless Integration with Client-Side Technologies</h2><p>While Java is used on the server side, the client side typically involves HTML, CSS, and JavaScript. Ensuring that Java components integrate seamlessly with these technologies and that the framework can support rich client-side interactions is challenging. This includes providing support for Ajax and JavaScript callbacks and updating UI parts without refreshing the entire page.</p><h2 id="security-concerns">Security Concerns</h2><p>Component-oriented frameworks need to manage security at the component level, ensuring that components do not expose vulnerabilities (such as cross-site scripting (XSS) or cross-site request forgery (CSRF)) and that the application can securely manage user data and authentication across components.</p><h2 id="usability-and-developer-productivity">Usability and Developer Productivity</h2><p>The framework should be robust, flexible, and easy to use. Providing a high level of abstraction while allowing developers enough control to customise component behaviour and appearance is a delicate balance. Documentation, tooling, and community support are crucial for developer adoption and productivity.</p><h2 id="scalability">Scalability</h2><p>As web applications grow, they must efficiently handle increasing loads and concurrent users. Component-oriented frameworks must ensure that components do not become bottlenecks and that the application can scale horizontally across multiple servers or instances.</p><p>Addressing these challenges requires a thoughtful design of the framework&rsquo;s architecture, focusing on efficiency, flexibility, and ease of use to make the development of complex web applications feasible and efficient.</p><h1 id="what-is-apache-tapestry">What is Apache Tapestry?</h1><p>Apache Tapestry is an open-source component-based web application framework for Java. It is designed to simplify the development of complex web applications by providing a highly scalable, efficient, and expressive programming model. Tapestry focuses on high productivity and maintainable code, adhering to the &ldquo;convention over configuration&rdquo; principle, which minimises the need for extensive XML configuration files commonly seen in older Java frameworks.</p><p><strong>Component-Based Architecture</strong> : Tapestry structures applications as a collection of components. Each component has its template (typically an HTML file) and an optional Java class that manages its behaviour. This model encourages reusable UI segments and separation of concerns.</p><p><strong>Live Class Reloading</strong> : One of Tapestry&rsquo;s standout features is its ability to reload Java classes automatically whenever they are changed. This means developers can modify the code and see the effects immediately in the browser without restarting the server, enhancing developer productivity.</p><p><strong>Convention Over Configuration</strong> : Tapestry reduces the need for explicit configuration by following convention over configuration. For example, it automatically detects templates based on naming conventions. This approach minimises boilerplate and setup code, making projects easier to manage and quicker to develop.</p><p><strong>High Performance</strong> : Tapestry includes built-in support for aggressive caching and minimisation of server-side processing, which enhances the application&rsquo;s performance. It generates compact, efficient JavaScript code and CSS, optimising applications&rsquo; load time and runtime performance.</p><p><strong>Inbuilt Ajax Support</strong> : Tapestry supports Ajax, allowing developers to quickly update parts of the page and manage client-server communication asynchronously. This is handled in a way that abstracts much of the complexity associated with Ajax in raw JavaScript.</p><p><strong>Powerful Templating</strong> : Each Tapestry component can have its HTML template, allowing designers and developers to work together seamlessly. The framework automatically links HTML templates with their corresponding Java classes, providing a robust foundation for clean, maintainable code.</p><p><strong>Dependency Injection and IoC Container</strong> : Tapestry integrates a powerful Inversion of Control (IoC) container that manages the lifecycle of application objects, centralising configuration and promoting loose coupling. This IoC container also supports dependency injection, simplifying the management of dependencies within the application.</p><h2 id="use-cases">Use Cases</h2><p>Tapestry is particularly well-suited for developers looking for a Java-based framework that supports rapid development cycles and needs a scalable solution for enterprise-grade applications. Its strong support for AJAX and client-side interactions makes it suitable for applications requiring rich, dynamic user interfaces.</p><h1 id="what-is-apache-wicket">What is Apache Wicket?</h1><p>Apache Wicket, often called Wicket, is a component-based web application framework for the Java programming language. It distinguishes itself with a strong emphasis on simplicity, ease of use, and a clean separation between logic and markup. Wicket allows developers to write applications in pure Java using components without the need for extensive XML configuration files.</p><h2 id="key-features-of-apache-wicket">Key Features of Apache Wicket</h2><p><strong>Component-Based Architecture</strong> : Wicket is built around a component-based model that treats pages and their elements as reusable components. These components are backed by Java classes and associated HTML templates. This design enables the encapsulation of behaviour and promotes reuse.</p><p><strong>Pure HTML Templates</strong> : Wicket uses pure HTML for its templates, allowing designers to create and edit them without needing to understand or embed any particular syntax or placeholders. Java components dynamically bind to these HTML templates via Wicket-specific attributes, keeping the development clean and straightforward.</p><p><strong>Strong Separation of Concerns</strong> : The framework promotes a clear separation between markup (HTML) and business logic (Java), making it easier for designers and developers to collaborate. Changes to the page layout do not typically require changes in the Java code.</p><p><strong>Event-driven</strong> : Components in Wicket are event-driven, similar to Java Swing or AWT. They handle events through callback methods, simplifying the management of user interactions and partial page updates.</p><p><strong>Stateful or Stateless</strong> : Wicket supports stateful components (preserving state across requests) and stateless components (where each request is treated as a new interaction), giving developers flexibility based on their application requirements.</p><p><strong>AJAX Support</strong> : The framework has built-in support for AJAX, allowing developers to update parts of a web page asynchronously without refreshing the entire page. This seamless integration, with Wicket managing much of the complexity behind the scenes.</p><p><strong>Security Features</strong> : Wicket is designed with security in mind, providing features to prevent common vulnerabilities such as Cross-Site Scripting (XSS) and Cross-Site Request Forgery (CSRF). These protections are built into the framework and enabled by default.</p><p><strong>Testability</strong> : The Wicket was designed to be testable at the unit and integration levels. It provides mock objects and testing mechanisms to simulate user interaction and application flow without requiring a running web server.</p><h2 id="use-cases-1">Use Cases</h2><p>Wicket is suitable for developers who prefer a pure Java approach to web development without relying heavily on JavaScript. It&rsquo;s particularly appealing in environments where application maintainability and a clear separation between code and design are priorities. This makes it ideal for enterprise applications where long-term maintainability and scalability are critical.</p><h1 id="vaadin-plattform">Vaadin Plattform</h1><p>The Vaadin Platform is a comprehensive development suite that simplifies building modern web applications. It is especially favoured for its ability to enable the creation of user interfaces (UIs) using Java, a language widely used across various industries, particularly in enterprise settings. Here are some key components and features of the Vaadin Platform:</p><p><strong>Vaadin Flow</strong> : This is the core framework for building web UIs in Java. Flow lets developers manage the UI and its interactions on the server side, automating the communication between the client (browser) and server.</p><p><strong>Vaadin Components</strong> : Vaadin comes with a rich set of built-in components, such as buttons, text fields, grids, and charts, ready to use out of the box. These components are built on web components, making them modern and highly interactive.</p><p><strong>Vaadin Fusion</strong> : Targeted at developers who prefer client-side development, Fusion allows building web applications using TypeScript and leveraging the same backend APIs that Flow uses. This supports a more traditional approach to web development using JavaScript/TypeScript for the frontend while maintaining Java for the backend.</p><p><strong>Themes and Layouts</strong> : Vaadin supports customisable themes and layouts, allowing developers to tailor the appearance of their applications to match specific branding or aesthetic requirements. It uses standard CSS for styling.</p><p><strong>Tools and Integrations</strong> : Vaadin provides various tools and integrations for popular IDEs (like Eclipse, IntelliJ IDEA, and VS Code), build tools (such as Maven and Gradle), and frameworks (like Spring). This integration helps streamline the development process, making it more efficient.</p><p><strong>End-to-End Development</strong> : From front-end components to back-end integration, Vaadin aims to provide a full-stack development experience. It supports building applications with responsive design, accessibility features, and internationalisation.</p><p><strong>Community and Pro Add-ons</strong> : The Vaadin platform also offers a range of community-driven and professional add-ons that extend the functionality of the basic framework. These can include advanced data components, tools for enhanced productivity, and integrations with other systems.</p><h2 id="vaadin-flow">Vaadin Flow</h2><p>Vaadin Flow is a Java framework for building modern web applications. Flow is specifically designed to let developers build web UIs in pure Java without needing HTML or JavaScript, though these can still be used if desired.</p><p>Here&rsquo;s how Vaadin Flow works:</p><p><strong>Server-Side Java</strong> : The application&rsquo;s logic is written entirely in Java. Vaadin Flow automatically manages the communication between the client-side (browser) and the server-side.</p><p><strong>Components and Layouts</strong> : Flow provides a range of UI components (like buttons, grids, and forms) and layouts that are used to construct the application interface.</p><p><strong>Data Binding</strong> : It supports robust data binding capabilities, making it easy to connect UI components to data sources and synchronising data between the server and the client.</p><p><strong>Routing and Navigation</strong> : Vaadin Flow handles routing and navigation within the application, managing different views that users interact with.</p><p>Vaadin Flow is ideal for developers familiar with Java and prefer to work in a server-side programming environment. It allows them to create rich, interactive web applications while remaining trendy in enterprise environments where Java is a common choice for backend development.</p><h1 id="the-difference-between-apache-tapestry-apache-wicket-and-vaadin-flow">The Difference between Apache Tapestry, Apache Wicket and Vaadin Flow</h1><p>Apache Tapestry, Apache Wicket, and Vaadin Flow are all Java-based frameworks designed to facilitate the development of web applications through a component-oriented approach. However, they each have distinct characteristics, philosophies, and design implementations. Here&rsquo;s a detailed comparison:</p><h2 id="apache-tapestry">Apache Tapestry</h2><p><strong>Component Model</strong> : Tapestry uses a model where pages and components are Java objects that are mapped to templates defined in HTML files. Each component is highly reusable and can be embedded within other components.</p><p><strong>Templates and Scripting</strong> : Tapestry firmly separates Java code from the HTML templates, promoting clean architecture and allowing designers to work on HTML/CSS without needing to understand Java.</p><p><strong>Conventions over Configuration</strong> : Tapestry relies heavily on convention over configuration, reducing the need for XML configuration files and aiming for zero configuration where possible.</p><p><strong>Live Class Reloading</strong> : One of Tapestry&rsquo;s hallmark features is its live class reloading capability, which allows developers to change code and see results immediately without restarting the server.</p><p><strong>Performance</strong> : Tapestry includes built-in support for JavaScript and AJAX without requiring deep developer knowledge, automatically optimising client-server communication.</p><h2 id="apache-wicket">Apache Wicket</h2><p><strong>Component Model</strong> : Wicket treats web pages as a hierarchy of components, each backed by its own Java class and usually associated with an HTML template that defines its layout.</p><p><strong>State Management</strong> : Wicket is known for being a stateful framework by default, which means it automatically manages the state across requests, but it can also be configured to be stateless.</p><p><strong>HTML and Java Integration</strong> : Wicket allows dynamic content to be written in pure HTML, with placeholders linked to Java components. This enables a clean separation between the presentation layer and business logic.</p><p><strong>Event Model</strong> : It follows an event-driven model similar to desktop GUI programming, making it intuitive for developers with experience in such environments.</p><p><strong>Testability</strong> : Wicket is designed to be highly testable with support for mocking and easy unit testing of individual components.</p><h2 id="vaadin-flow-1">Vaadin Flow</h2><p><strong>Component Model</strong> : Vaadin Flow allows developers to build their UI entirely in Java or combine Java and templates. Vaadin 14 introduced LitTemplate, which lets developers define components using TypeScript and LitHTML.</p><p><strong>Client-Server Communication</strong> : Flow abstracts the client-server communication completely, allowing developers to focus on server-side Java without worrying about the frontend, which is automatically handled.</p><p><strong>Rich Widget Set</strong> : Vaadin has a comprehensive set of highly interactive UI components that resemble desktop functionality.</p><p><strong>Themes and Layouts</strong> : It supports powerful theming capabilities with CSS and theming constructs that are easy to apply across different components.</p><p><strong>Performance and Scalability</strong> : Vaadin manages client-server communication efficiently but can be challenged by the large amount of data sent over the network, especially in complex applications with numerous UI components.</p><h2 id="key-differentiators">Key Differentiators</h2><p><strong>Approach to HTML/Java Separation</strong> : Tapestry and Wicket encourage more direct involvement with HTML, facilitating collaboration between designers and developers. Vaadin, in contrast, allows developers to work almost entirely in Java, which might streamline development but can distance the design process from HTML/CSS.</p><p><strong>State Management</strong> : Wicket is inherently stateful, which is beneficial for certain applications but can impact performance and scalability. Tapestry and Vaadin manage the state more transparently and can be more scalable in large applications.</p><p><strong>Ease of Use and Learning Curve</strong> : Vaadin is often considered easier to learn for Java developers since it requires less HTML and JavaScript knowledge. Tapestry and Wicket have steeper learning curves but offer more control and flexibility over the UI.</p><p><strong>Live Reloading</strong> : Tapestry&rsquo;s live class reloading is particularly useful for rapid development cycles, a feature not inherently present in Wicket or Vaadin.</p><p>Choosing between these frameworks depends mainly on the project requirements, team skills, and specific needs regarding application scalability, maintainability, and development speed. Each framework has strengths and can be the best choice in different scenarios.</p>
]]></content:encoded><category>Java</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2024/04/68907F37-7FA3-4273-950D-394AE64CC928.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/04/68907F37-7FA3-4273-950D-394AE64CC928.jpg"/><enclosure url="https://svenruppert.com/images/2024/04/68907F37-7FA3-4273-950D-394AE64CC928.jpg" type="image/jpeg" length="0"/></item><item><title>CWE-1123: Excessive Use of Self-Modifying Code for Java Developers</title><link>https://svenruppert.com/posts/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/</link><pubDate>Thu, 12 Sep 2024 11:19:19 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/</guid><description>Self-modifying code refers to a type of code that alters its own instructions while it is executing. While this practice can offer certain advantages, such as optimisation and adaptability, it is generally discouraged due to the significant risks and challenges it introduces. For Java developers, using self-modifying code is particularly problematic because it undermines the codebase&rsquo;s predictability, readability, and maintainability, and Java as a language does not natively support self-modification of its code.</description><content:encoded>&lt;![CDATA[<p>Self-modifying code refers to a type of code that alters its own instructions while it is executing. While this practice can offer certain advantages, such as optimisation and adaptability, it is generally discouraged due to the significant risks and challenges it introduces. For Java developers, using self-modifying code is particularly problematic because it undermines the codebase&rsquo;s predictability, readability, and maintainability, and Java as a language does not natively support self-modification of its code.</p><ol><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#risks">Risks</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#examples-of-risky-practices">Examples of Risky Practices</a><ol><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#example">Example</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#mitigation-strategies">Mitigation Strategies</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#example">Example</a><ol><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#mitigation-strategies">Mitigation Strategies</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#mitigation-strategies">Mitigation Strategies</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#cve-2014-0114">CVE-2014-0114</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#cve-2013-2423">CVE-2013-2423</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#cve-2015-1832">CVE-2015-1832</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#cve-2012-0507">CVE-2012-0507</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#cve-2019-12384">CVE-2019-12384</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#mitigation-strategies">Mitigation Strategies</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#code-injection-attacks">Code Injection Attacks</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#remote-code-execution-rce">Remote Code Execution (RCE)</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#privilege-escalation">Privilege Escalation</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#dynamic-code-loading-attacks">Dynamic Code Loading Attacks</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#polymorphic-malware">Polymorphic Malware</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#evasion-of-security-mechanisms">Evasion of Security Mechanisms</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#backdoors-and-rootkits">Backdoors and Rootkits</a></li><li><a href="https://svenruppert.com/2024/09/12/cwe-1123-excessive-use-of-self-modifying-code-for-java-developers/#tampering-with-security-features">Tampering with Security Features</a></li></ol><h3 id="risks">Risks</h3><p><strong>Unpredictable Behavior:</strong> Self-modifying code can lead to unexpected program behaviour, making diagnosing and fixing bugs difficult.</p><p><strong>Security Vulnerabilities:</strong> Code that modifies itself can be a vector for various security attacks, including injection attacks and malware.</p><p><strong>Maintenance Difficulty:</strong> Such code is difficult to read and understand, making it more difficult to maintain and update.</p><p><strong>Performance Issues:</strong> Self-modifying code can cause performance degradation due to the additional overhead of modifying and interpreting the changes at runtime.</p><h3 id="examples-of-risky-practices">Examples of Risky Practices</h3><p><strong>Dynamic Class Loading:</strong> Java allows classes to be loaded at runtime using mechanisms such as reflection or custom class loaders. While dynamic class loading itself is not inherently wrong, using it excessively or without apparent necessity can lead to self-modifying behaviour.</p><p><strong>Bytecode Manipulation:</strong> Using libraries like ASM or Javassist to modify Java bytecode at runtime can lead to self-modifying code. This practice is highly discouraged unless essential.</p><p><strong>Reflection:</strong> While reflection is a powerful feature, it can be misused to modify private fields, methods, or classes, leading to behaviour that is hard to trace and debug.</p><h4 id="example">Example</h4><p>An example of risky self-modifying behaviour in Java using bytecode manipulation:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javassist.ClassPool</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javassist.CtClass</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javassist.CtMethod</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SelfModifyingExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ClassPool</span><span class="w"/><span class="n">pool</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ClassPool</span><span class="p">.</span><span class="na">getDefault</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">CtClass</span><span class="w"/><span class="n">cc</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">pool</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"TargetClass"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">CtMethod</span><span class="w"/><span class="n">m</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">cc</span><span class="p">.</span><span class="na">getDeclaredMethod</span><span class="p">(</span><span class="s">"targetMethod"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">m</span><span class="p">.</span><span class="na">insertBefore</span><span class="p">(</span><span class="s">"{ System.out.println(\"Method modified at runtime\"); }"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">cc</span><span class="p">.</span><span class="na">toClass</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">TargetClass</span><span class="w"/><span class="n">target</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TargetClass</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">target</span><span class="p">.</span><span class="na">targetMethod</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">TargetClass</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">targetMethod</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Original method execution"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the TargetClass method targetMethod is modified at runtime to include an additional print statement. This kind of modification can lead to the aforementioned risks.</p><h4 id="mitigation-strategies">Mitigation Strategies</h4><p><strong>Avoid Runtime Code Modifications:</strong> Design your system in a way that minimises or eliminates the need for runtime code modifications.</p><p><strong>Use Design Patterns:</strong> Employ design patterns such as Strategy or State patterns that allow behaviour changes without altering the code at runtime.</p><p><strong>Proper Use of Reflection:</strong> Use reflection sparingly and only when no other viable solution exists. Document its usage thoroughly.</p><p><strong>Static Code Analysis:</strong> Use static code analysis tools to detect and prevent the introduction of self-modifying code.</p><p>Excessive use of self-modifying code in Java is fraught with risks that can compromise your applications&rsquo; security, maintainability, and performance. By adhering to best practices and using design patterns that promote flexibility and adaptability without modifying code at runtime, you can avoid the pitfalls associated with CWE-1123.</p><h2 id="a-reflection-example">A Reflection Example</h2><p>Reflection in Java allows for introspection and manipulation of classes, fields, methods, and constructors at runtime. While powerful, excessive or improper use of reflection can lead to self-modifying behaviours, which aligns with CWE-1123. This can result in unpredictable behaviour, security vulnerabilities, and maintenance challenges.</p><h3 id="example-1">Example</h3><p>Below is an example demonstrating the excessive use of reflection to modify a class&rsquo;s behaviour at runtime, which can be considered a form of self-modifying code.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.lang.reflect.Field</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.lang.reflect.Method</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ReflectionExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Original object creation</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">MyClass</span><span class="w"/><span class="n">original</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">MyClass</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">original</span><span class="p">.</span><span class="na">printMessage</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Using reflection to modify the behavior at runtime</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Class</span><span class="o">&lt;?&gt;</span><span class="w"/><span class="n">clazz</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Class</span><span class="p">.</span><span class="na">forName</span><span class="p">(</span><span class="s">"MyClass"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Method</span><span class="w"/><span class="n">method</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">clazz</span><span class="p">.</span><span class="na">getDeclaredMethod</span><span class="p">(</span><span class="s">"setMessage"</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">method</span><span class="p">.</span><span class="na">setAccessible</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Modify the private field value using reflection</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Field</span><span class="w"/><span class="n">field</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">clazz</span><span class="p">.</span><span class="na">getDeclaredField</span><span class="p">(</span><span class="s">"message"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">field</span><span class="p">.</span><span class="na">setAccessible</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">field</span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="n">original</span><span class="p">,</span><span class="w"/><span class="s">"Modified message"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Verify the modification</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">original</span><span class="p">.</span><span class="na">printMessage</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">MyClass</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">message</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Original message"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">printMessage</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">message</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setMessage</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">message</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">message</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">message</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the ReflectionExample class:</p><p>Creates an instance of MyClass and prints the original message. It uses reflection to modify the private field message and the private method setMessage of MyClass. Changes the value of the message field and prints the modified message.</p><p>This example showcases how reflection can alter an object&rsquo;s behaviour and state at runtime, leading to the issues outlined in CWE-1123.</p><h4 id="mitigation-strategies-1">Mitigation Strategies</h4><p><strong>Minimise Reflection Use:</strong> Avoid using reflection unless absolutely necessary. Prefer alternative design patterns that allow for flexibility without modifying the code at runtime.</p><p><strong>Access Control:</strong> Ensure that fields and methods that should not be modified are kept private and final where possible to prevent unintended access.</p><p><strong>Static Analysis Tools:</strong> Use static analysis tools to detect excessive use of reflection and other risky practices in the codebase.</p><p><strong>Code Reviews:</strong> Conduct thorough code reviews to identify and mitigate the use of self-modifying code through reflection.</p><p>Reflection is a powerful tool in Java, but misuse can lead to the risks associated with CWE-1123. By adhering to best practices and minimising the use of reflection to modify code at runtime, developers can maintain the security, predictability, and maintainability of their applications.</p><h2 id="example-of-dynamic-class-loading">Example of Dynamic Class Loading</h2><p>Dynamic class loading in Java refers to the ability to load and unload classes at runtime. While this can be useful in specific scenarios, excessive or improper use can lead to self-modifying code behaviours, which align with CWE-1123. This can introduce risks such as unpredictable behaviour, security vulnerabilities, and maintenance challenges.</p><p>Below is an example demonstrating the excessive use of dynamic class loading to modify a class&rsquo;s behaviour at runtime, which can be considered a form of self-modifying code.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">DynamicClassLoadingExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Load the original class</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ClassLoader</span><span class="w"/><span class="n">classLoader</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">DynamicClassLoadingExample</span><span class="p">.</span><span class="na">class</span><span class="p">.</span><span class="na">getClassLoader</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Class</span><span class="o">&lt;?&gt;</span><span class="w"/><span class="n">loadedClass</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">classLoader</span><span class="p">.</span><span class="na">loadClass</span><span class="p">(</span><span class="s">"MyClass"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Create an instance of the loaded class</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Object</span><span class="w"/><span class="n">instance</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">loadedClass</span><span class="p">.</span><span class="na">getDeclaredConstructor</span><span class="p">().</span><span class="na">newInstance</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Invoke the original method</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">loadedClass</span><span class="p">.</span><span class="na">getMethod</span><span class="p">(</span><span class="s">"printMessage"</span><span class="p">).</span><span class="na">invoke</span><span class="p">(</span><span class="n">instance</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Dynamically load the modified class</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">classLoader</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">CustomClassLoader</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">loadedClass</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">classLoader</span><span class="p">.</span><span class="na">loadClass</span><span class="p">(</span><span class="s">"ModifiedClass"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Create an instance of the modified class</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">instance</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">loadedClass</span><span class="p">.</span><span class="na">getDeclaredConstructor</span><span class="p">().</span><span class="na">newInstance</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Invoke the modified method</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">loadedClass</span><span class="p">.</span><span class="na">getMethod</span><span class="p">(</span><span class="s">"printMessage"</span><span class="p">).</span><span class="na">invoke</span><span class="p">(</span><span class="n">instance</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Original class definition</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">MyClass</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">printMessage</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Original message"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Custom class loader to simulate loading a modified class</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">CustomClassLoader</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">ClassLoader</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">Class</span><span class="o">&lt;?&gt;</span><span class="w"/><span class="n">loadClass</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">ClassNotFoundException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="s">"ModifiedClass"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">name</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Define a modified version of MyClass at runtime</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">modifiedClassName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"ModifiedClass"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">modifiedClassBody</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"public class "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">modifiedClassName</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" {"</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">" public void printMessage() {"</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">" System.out.println(\"Modified message\");"</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">" }"</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"}"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">classData</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">compileClass</span><span class="p">(</span><span class="n">modifiedClassName</span><span class="p">,</span><span class="w"/><span class="n">modifiedClassBody</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">defineClass</span><span class="p">(</span><span class="n">modifiedClassName</span><span class="p">,</span><span class="w"/><span class="n">classData</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">classData</span><span class="p">.</span><span class="na">length</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kd">super</span><span class="p">.</span><span class="na">loadClass</span><span class="p">(</span><span class="n">name</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="nf">compileClass</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">className</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">classBody</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Simulate compiling the class body into bytecode (in a real scenario, use a compiler API)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// This is a placeholder for demonstration purposes</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">classBody</span><span class="p">.</span><span class="na">getBytes</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the DynamicClassLoadingExample class:</p><p>Loads an original class MyClass and invokes its printMessage method. Dynamically loads a modified version of the class, ModifiedClass, using a custom class loader. Creates an instance of the modified class and invokes its printMessage method, which prints a different message.</p><p>This example showcases how dynamic class loading can alter a program&rsquo;s behaviour at runtime, leading to the issues outlined in CWE-1123.</p><h3 id="mitigation-strategies-2">Mitigation Strategies</h3><p><strong>Avoid Unnecessary Dynamic Loading:</strong> Use dynamic class loading only when it is indispensable and cannot be avoided through other design patterns.</p><p><strong>Secure Class Loaders:</strong> Ensure custom class loaders are secure and do not load untrusted or malicious classes.</p><p><strong>Static Analysis Tools:</strong> Use static analysis tools to detect excessive use of dynamic class loading and other risky practices in the codebase.</p><p><strong>Code Reviews:</strong> Conduct thorough code reviews to identify and mitigate the use of self-modifying code through dynamic class loading.</p><h2 id="java-based-cves-based-on-cwe-1123">Java-based CVEs based on CWE-1123</h2><p>While there might not be specific CVEs (Common Vulnerabilities and Exposures) explicitly labelled as being caused by CWE-1123 (Excessive Use of Self-Modifying Code), several Java-related vulnerabilities can arise from practices associated with self-modifying code. These typically involve dynamic class loading, reflection, and bytecode manipulation issues. Here are some examples of Java-based CVEs that relate to these practices:</p><h3 id="cve-2014-0114">CVE-2014-0114</h3><p><strong>Description:</strong> Apache Commons Collections Remote Code Execution Vulnerability</p><p><strong>Issue:</strong> This vulnerability involves using reflection to manipulate serialised data, leading to arbitrary code execution. It was found in the Apache Commons Collections library, where certain classes could be used to execute arbitrary code when deserialised. This is a form of self-modifying behaviour, as the serialised data could alter the program&rsquo;s execution flow.</p><p><strong>Impact:</strong> Attackers could exploit this vulnerability to execute arbitrary commands on the server running the vulnerable application.</p><h3 id="cve-2013-2423">CVE-2013-2423</h3><p><strong>Description:</strong> Oracle Java SE Remote Code Execution Vulnerability</p><p><strong>Issue:</strong> This vulnerability arises from improper handling of certain methods in Java, leading to the execution of arbitrary code. It leverages reflection and class-loading mechanisms to inject and execute malicious code.</p><p><strong>Impact:</strong> Exploiting this vulnerability allows remote attackers to execute arbitrary code on the affected system, potentially leading to total system compromise.</p><h3 id="cve-2015-1832">CVE-2015-1832</h3><p><strong>Description:</strong> Android Remote Code Execution Vulnerability in Apache Cordova</p><p><strong>Issue:</strong> This vulnerability involves dynamic class loading and improper validation of inputs. It allowed attackers to inject malicious code into an Android application built with Apache Cordova by exploiting the WebView component.</p><p><strong>Impact:</strong> Successful exploitation could result in arbitrary code execution within the context of the affected application, leading to potential data leakage or further exploitation.</p><h3 id="cve-2012-0507">CVE-2012-0507</h3><p><strong>Description:</strong> Oracle Java SE Remote Code Execution Vulnerability</p><p><strong>Issue:</strong> This vulnerability involves using reflection and dynamic class loading to exploit a flaw in the Java Runtime Environment (JRE). The vulnerability allows an untrusted Java applet to break out of the Java sandbox and execute arbitrary code.</p><p><strong>Impact:</strong> Exploiting this vulnerability could allow an attacker to execute arbitrary code on the host system with the user&rsquo;s privileges running the Java applet.</p><h3 id="cve-2019-12384">CVE-2019-12384</h3><p><strong>Description:</strong> FasterXML jackson-databind Deserialization Vulnerability</p><p><strong>Issue:</strong> This vulnerability involves the unsafe handling of deserialisation using the jackson-databind library. By exploiting polymorphic type handling, attackers could inject malicious code that gets executed during deserialisation.</p><p><strong>Impact:</strong> Successful exploitation could result in arbitrary code execution, leading to potential data breaches and system compromise.</p><h3 id="mitigation-strategies-3">Mitigation Strategies</h3><p><strong>Avoid Self-Modifying Code Practices:</strong> Do not use dynamic class loading, reflection, or bytecode manipulation unless absolutely necessary. When required, ensure proper validation and security measures are in place.</p><p><strong>Use Safe Deserialisation:</strong> Avoid deserialisation of untrusted data. If deserialisation is necessary, libraries and techniques that enforce strict type checking and validation should be used.</p><p><strong>Apply Security Patches:</strong> Regularly update and patch libraries and frameworks to protect against known vulnerabilities.</p><p><strong>Code Reviews and Static Analysis:</strong> Conduct thorough code reviews and use static analysis tools to detect and mitigate the use of risky code practices.</p><p><strong>Security Best Practices:</strong> To reduce the attack surface, follow security best practices, such as least privilege, input validation, and secure coding guidelines.</p><h2 id="what-kind-of-attacks-or-infection-methods-are-based-on-cwe-1123">What kind of attacks or infection methods are based on CWE-1123?</h2><p>CWE-1123 (Excessive Use of Self-Modifying Code) can lead to several types of attacks and infection methods due to such code&rsquo;s unpredictable and dynamic nature. Here are some common attack vectors and infection methods associated with this vulnerability:</p><h3 id="code-injection-attacks">Code Injection Attacks</h3><p>Attackers exploit self-modifying code to inject malicious code into a program. This can occur through various means, such as manipulating input data that gets executed or modifying code at runtime to include harmful payloads.</p><p><strong>Example:</strong></p><p><strong>SQL Injection:</strong> If an application dynamically constructs SQL queries using user input and modifies these queries at runtime, an attacker can inject malicious SQL commands to alter the behaviour of the database operations.</p><h3 id="remote-code-execution-rce">Remote Code Execution (RCE)</h3><p>Self-modifying code can enable Remote Code Execution by allowing attackers to modify or load classes and methods at runtime. This makes it easier to introduce and execute arbitrary code.</p><p><strong>Example:</strong></p><p><strong>Deserialization Vulnerabilities:</strong> When an application deserialises data without proper validation, an attacker can inject objects that modify the code flow, leading to the execution of arbitrary code.</p><h3 id="privilege-escalation">Privilege Escalation</h3><p>Attackers can exploit self-modifying code to escalate their privileges within a system. They can bypass security checks and gain higher-level access by dynamically altering the code.</p><p><strong>Example:</strong></p><p><strong>Reflection Attacks:</strong> Using reflection, attackers can modify private fields and methods to escalate privileges, accessing parts of the system that would otherwise be restricted.</p><h3 id="dynamic-code-loading-attacks">Dynamic Code Loading Attacks</h3><p>Self-modifying code often involves dynamic loading of classes or bytecode manipulation, which can be exploited to load malicious code at runtime.</p><p><strong>Example:</strong></p><p><strong>Dynamic Class Loading:</strong> Attackers can trick the application into loading a malicious class that performs unwanted actions, such as exfiltrating data or modifying system files.</p><h3 id="polymorphic-malware">Polymorphic Malware</h3><p>Self-modifying code is commonly used in polymorphic malware, where the malware changes its code to evade detection by security software.</p><p><strong>Example:</strong></p><p><strong>Polymorphic Virus:</strong> A virus that encrypts its payload and changes its decryption routine with each infection, making it difficult for antivirus programs to detect the malware&rsquo;s signature.</p><h3 id="evasion-of-security-mechanisms">Evasion of Security Mechanisms</h3><p>Self-modifying code can be used to evade security mechanisms such as firewalls, intrusion detection systems (IDS), and antivirus software by altering its code structure dynamically.</p><p><strong>Example:</strong></p><p><strong>Metamorphic Malware:</strong> Similar to polymorphic malware, metamorphic malware reprograms itself completely with each infection, ensuring that no two copies of the malware are identical, thus evading signature-based detection.</p><h3 id="backdoors-and-rootkits">Backdoors and Rootkits</h3><p>Attackers can use self-modifying code to install backdoors or rootkits that alter the behaviour of the operating system or application to provide persistent unauthorised access.</p><p><strong>Example:</strong></p><p><strong>Rootkits:</strong> A rootkit can use self-modifying code to hide its presence by altering kernel or application code to prevent detection by security tools.</p><h3 id="tampering-with-security-features">Tampering with Security Features</h3><p>Self-modifying code can be used to tamper with security features such as authentication mechanisms, encryption routines, and access controls.</p><p><strong>Example:</strong></p><p><strong>Tampering with Authentication:</strong> By dynamically modifying authentication checks, an attacker can bypass login mechanisms and gain unauthorised access to the system.</p><p>By understanding these attack vectors and implementing mitigation strategies, developers and security professionals can reduce the risks associated with self-modifying code and improve the overall security of their applications.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Security</category><media:content url="https://svenruppert.com/images/2024/09/2DD17027-6231-4040-A9F0-89A4076945CC.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/09/2DD17027-6231-4040-A9F0-89A4076945CC.jpg"/><enclosure url="https://svenruppert.com/images/2024/09/2DD17027-6231-4040-A9F0-89A4076945CC.jpg" type="image/jpeg" length="0"/></item><item><title>IoT with TinkerForge and Java</title><link>https://svenruppert.com/posts/iot-with-tinkerforge-and-java/</link><pubDate>Wed, 04 Sep 2024 12:37:30 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/iot-with-tinkerforge-and-java/</guid><description>Introduction TinkerForge is an innovative and modular hardware system that allows users to build electronic devices quickly and flexibly. Whether you&rsquo;re an experienced engineer, a hobbyist, or a complete newbie, TinkerForge offers a range of components that can be easily connected and programmed, allowing for rapid prototyping and the creation of custom electronics projects. Since its launch, TinkerForge has gained popularity in various areas, including education, research, and industrial automation, due to its user-friendly design and extensive feature set.</description><content:encoded>&lt;![CDATA[<h2 id="introduction">Introduction</h2><p>TinkerForge is an innovative and modular hardware system that allows users to build electronic devices quickly and flexibly. Whether you&rsquo;re an experienced engineer, a hobbyist, or a complete newbie, TinkerForge offers a range of components that can be easily connected and programmed, allowing for rapid prototyping and the creation of custom electronics projects. Since its launch, TinkerForge has gained popularity in various areas, including education, research, and industrial automation, due to its user-friendly design and extensive feature set.</p><ol><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#introduction">Introduction</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#the-origins-and-evolution-of-tinkerforge">The origins and evolution of TinkerForge</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#tinkerforge-architecture">TinkerForge architecture</a><ol><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#bricks">Bricks</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#bricklets">Bricklets</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#master-extensions">Master-Extensions</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#programming-and-control">Programming and control</a><ol><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#tinkerforge-api">TinkerForge-API</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#tinkerforge-gui">TinkerForge-GUI</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#remote-access-and-control">Remote access and control</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#applications-of-tinkerforge">Applications of TinkerForge</a><ol><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#training">Training</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#prototyping">Prototyping</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#industrial-automation">Industrial automation</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#diy-projects-and-maker-community">DIY projects and maker community</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#advantages-of-tinkerforge">Advantages of TinkerForge</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#limitations-and-challenges">Limitations and challenges</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#how-do-i-start-programming-tinkerforge-electronics-with-java">How do I start programming TinkerForge electronics with Java?</a><ol><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#requirements">Requirements</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#tinkerforge-architecture-1">TinkerForge architecture</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#connecting-your-tinkerforge-hardware">Connecting your TinkerForge hardware</a><ol><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#connect-bricklets-to-the-master-brick">Connect Bricklets to the Master Brick</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#connect-the-master-brick-to-the-computer">Connect the master brick to the computer.</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#installing-the-tinkerforge-java-api">Installing the TinkerForge Java API</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#writing-the-first-java-program">Writing the first Java program</a><ol><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#running-and-testing-the-program">Running and testing the program</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#dealing-with-errors">Dealing with errors</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#event-driven-programming">Event-driven programming</a></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#controlling-actors">Controlling actors</a></li></ol></li><li><a href="https://svenruppert.com/2024/09/04/auto-draft/#conclusion">Conclusion</a></li></ol></li></ol><h2 id="the-origins-and-evolution-of-tinkerforge">The origins and evolution of TinkerForge</h2><p>TinkerForge was founded in 2011. The founders Matthias Bolte and Olaf Lüke imagined a system that would lower the hurdles in hardware development. Their goal was to provide a range of components that could be easily combined into complex systems without requiring in-depth knowledge of electronics or soldering skills.</p><p>The idea arose from the frustration the founders experienced while working on electronics projects, which often required a lot of time just to get the essential components to work together. They recognised that there was a need for a more intuitive and modular system that would allow anyone to create hardware projects quickly and effectively.</p><p>Over the years, TinkerForge has grown from a niche product into a widely used platform supported by a vibrant community of developers and manufacturers. The system is now used in various applications, from simple DIY projects to advanced industrial automation systems.</p><h2 id="tinkerforge-architecture">TinkerForge architecture</h2><p>The TinkerForge system is based on a modular architecture consisting of three main component types: Bricks, Bricklets and Master Extensions.</p><h3 id="bricks">Bricks</h3><p>Bricks are the core building blocks of the TinkerForge system. They are small, self-contained modules that provide specific functions, such as controlling motors, reading sensors, or connecting to a computer. Each brick has a specific purpose and can be easily attached using a stackable system.</p><p>The most common types of bricks include:</p><p><strong>Master Brick</strong> : The Master Brick is the central node that controls and supplies power to other Bricks and Bricklets. It provides the communication interface between the TinkerForge stack and a computer or microcontroller.</p><p><strong>ESP32 Brick</strong> : The ESP32 Brick offers six<a href="https://www.tinkerforge.com/de/doc/Primer.html#primer-bricklets">Bricklet</a> Connections and is equipped with a powerful ESP32 microcontroller.</p><p><strong>HAT-Brick</strong> : With the HAT Brick up to act, bricklets can be connected to a Raspberry Pi.</p><p><strong>IMU-Brick 2.0</strong> : The Inertial Measurement Unit Brick contains a 9 DOF (Degrees of Freedom) sensor that provides orientation data, making it suitable for navigation and motion-tracking applications.</p><h3 id="bricklets">Bricklets</h3><p>Bricklets are smaller modules that connect to bricks to expand their functionality. They are essentially sensors, actuators, or other peripheral devices that can be connected to a brick to provide additional input or output options. Bricklets are connected to Bricks via standardised connectors, and each can support multiple Bricklets.</p><p>Some examples of Bricklets are:</p><p><strong>Temperature Bricklet</strong> : This module measures temperature and can be used in projects requiring environmental monitoring.</p><p><strong>Distance IR Bricklet</strong> : This sensor measures the distance to an object using infrared light, which is helpful in robotics or object detection applications.</p><p><strong>LCD 20x4 Bricklet</strong> : A simple display module that enables text output, perfect for building user interfaces or displaying sensor data.</p><h3 id="master-extensions">Master-Extensions</h3><p>Master Extensions are additional boards that can be connected to the master brick to provide additional functionality or expand connectivity. These extensions allow the TinkerForge system to connect to various communication protocols or external devices.</p><p>Examples of master extensions are:</p><p><strong>WIFI extension 2.0</strong> : This module adds wireless communication capabilities to the TinkerForge system, allowing bricks to be controlled remotely via a computer or smartphone.</p><p><strong>RS485 extension</strong> : This extension enables long-distance communication via RS485, making it suitable for industrial environments where long-distance wired connections are required.</p><p><strong>Ethernet extension</strong> : This extension provides a wired Ethernet connection to the TinkerForge system, ensuring reliable and fast communication in networked environments.</p><h2 id="programming-and-control">Programming and control</h2><p>One of TinkerForge&rsquo;s outstanding features is its ease of programming. The system is designed to be accessible to users of all experience levels. TinkerForge offers various ways to control and program the Bricks and Bricklets, ensuring users can choose the best method.</p><h3 id="tinkerforge-api">TinkerForge-API</h3><p>TinkerForge provides a powerful and easy-to-use application programming interface (API) that allows users to control the Bricks and Bricklets from various programming languages ​​, including Python, C, C++, Java, Ruby and more. The API abstracts the complexity of hardware interaction and simplifies writing code that interacts with the various components.</p><p>The API is well-documented and includes examples for each supported language. This documentation provides detailed explanations of each feature, making it easier for users to understand how to control the hardware.</p><p><a href="https://www.tinkerforge.com/en/doc/Software/API_Bindings_Java.html">https://www.tinkerforge.com/en/doc/Software/API_Bindings_Java.html</a></p><h3 id="tinkerforge-gui">TinkerForge-GUI</h3><p>TinkerForge also offers a graphical user interface (GUI) called Brick Viewer for users who prefer a more visual approach. This software visually represents the connected Bricks and Bricklets and allows users to configure, monitor and control their devices without writing code.</p><p>Brick Viewer is handy for beginners who are just starting with the TinkerForge system. It allows users to experiment with the hardware, view sensor data in real-time, and adjust settings without understanding the underlying code.</p><p><a href="https://www.tinkerforge.com/de/doc/Software/Brickv.html#brickv">https://www.tinkerforge.com/de/doc/Software/Brickv.html#brickv</a></p><h3 id="remote-access-and-control">Remote access and control</h3><p>TinkerForge also supports remote access and control, allowing users to interact with their devices over a network. The WIFI or Ethernet Master extensions allow users to control their TinkerForge systems over the Internet from anywhere in the world. This feature is precious for projects that require remote monitoring or control, such as home automation systems or remote sensing applications.</p><h2 id="applications-of-tinkerforge">Applications of TinkerForge</h2><p>TinkerForge&rsquo;s versatility has led to its use in various applications. The most common areas of use for TinkerForge include:</p><h3 id="training">Training</h3><p>TinkerForge is an excellent tool for electronics and programming lessons. Its modular design allows students to experiment with different components and see immediate results. The ease of use and extensive documentation make it accessible to learners of all ages.</p><p>Educational institutions often use TinkerForge in robotics, embedded systems, and computer science courses. It allows students to create projects demonstrating key concepts such as sensor integration, motor control and data communication.</p><h3 id="prototyping">Prototyping</h3><p>One of TinkerForge&rsquo;s strengths is its ability to enable rapid prototyping. Engineers and designers can quickly assemble and test their ideas without complex wiring or soldering. The system&rsquo;s modular design means that components can be easily replaced or reconfigured as needed.</p><p>TinkerForge is often used in the early stages of product development, where speed and flexibility are crucial. Using TinkerForge, developers can iterate their designs faster, reducing the time to market for new products.</p><h3 id="industrial-automation">Industrial automation</h3><p>TinkerForge is also used in industrial automation applications where reliability and scalability are important. The system is suitable for use in industrial environments by supporting various communication protocols, such as RS485 and Ethernet.</p><p>In these environments, TinkerForge components can monitor and control machines, collect data from sensors, and automate processes. The modular design allows for easy expansion and customisation, making building complex systems tailored to specific requirements possible.</p><h3 id="diy-projects-and-maker-community">DIY projects and maker community</h3><p>TinkerForge has a strong following among DIYers and hobbyists. The system&rsquo;s simplicity and flexibility make it ideal for creating custom electronics projects, such as home automation systems, robotics, and art installations.</p><p>The maker community around TinkerForge is active, and users share their projects, code and ideas online. This community-focused approach has led to the development of a wide range of open-source projects and resources, further increasing the value of the TinkerForge ecosystem.</p><h2 id="advantages-of-tinkerforge">Advantages of TinkerForge</h2><p>TinkerForge offers several advantages that have contributed to its popularity:</p><p><strong>Modularity</strong> : The ability to mix and match components allows users to create highly customised systems without designing everything from scratch.</p><p><strong>Ease of use</strong> : With a straightforward API and visual interface, TinkerForge is accessible to users of all experience levels.</p><p><strong>Scalability</strong> : TinkerForge can be used in small, simple projects and large, complex systems. Its modular design allows it to be easily expanded as needed.</p><p><strong>Cross-platform support</strong> : TinkerForge&rsquo;s API is available in multiple programming languages, making it easy to integrate into existing projects.</p><p><strong>Community and support</strong> : A strong community and comprehensive documentation ensure users have access to the resources they need to succeed.</p><h2 id="limitations-and-challenges">Limitations and challenges</h2><p>Although TinkerForge is a powerful and flexible system, it also has its limitations:</p><p><strong>Costs</strong> : Although the price is reasonable, building complex systems with TinkerForge can be expensive, especially when multiple bricks and brackets are required.</p><p><strong>Limited computing power</strong> : Some bricks, especially the master brick, may not have the computing power required for demanding applications. In such cases, an external microcontroller or computer may be required.</p><p><strong>Learning curve</strong> : Although TinkerForge is designed to be user-friendly, there is still a learning curve, especially for those new to electronics or programming.</p><p><strong>Dependence on proprietary hardware</strong> : TinkerForge components are proprietary, meaning users are somewhat locked into the ecosystem. While the open API and documentation help mitigate this, it is worth considering for long-term projects.</p><p>TinkerForge represents a significant advancement in the field of modular electronics, providing a platform that bridges the gap between hardware and software. Its user-friendly design, modular architecture, and extensive documentation make it an ideal choice for a wide range of users.</p><h2 id="how-do-i-start-programming-tinkerforge-electronics-with-java">How do I start programming TinkerForge electronics with Java?</h2><h3 id="requirements">Requirements</h3><p>Before diving into Java programming with TinkerForge, you must have the following:</p><p><strong>Basic knowledge of Java</strong> : Familiarity with Java syntax and programming concepts.</p><p><strong>TinkerForge-Hardware</strong> : At least one<strong>Meister Brick</strong> and a<strong>Bricklet</strong> (e.g. Temperature Bricklet) for interaction.</p><p><strong>Computer with USB port</strong> : To connect the master brick to the computer.</p><p><strong>Internet connection</strong> : To download the required software and libraries.</p><h3 id="tinkerforge-architecture-1">TinkerForge architecture</h3><p>Before programming, it is essential to understand the vital components of the TinkerForge system:</p><p><strong>Master Bricks</strong> : The central controller that connects to your computer via USB. It manages communication with all connected Bricklets.</p><p><strong>Bricklets</strong> : Modular components that fulfil specific functions (e.g. sensors, actuators). They are connected to the master brick via standardised connectors.</p><p><strong>Stackable System</strong> : Multiple bricks and bricklets can be stacked to create complex systems without soldering.</p><h3 id="connecting-your-tinkerforge-hardware">Connecting your TinkerForge hardware</h3><h4 id="connect-bricklets-to-the-master-brick">Connect Bricklets to the Master Brick</h4><p>First, the Bricklets should be connected to the Master Brick. Care should be taken to ensure that the connection is tight and that the plugs&rsquo; pins are aligned correctly.</p><p><strong>Example setup</strong> :</p><ul><li>Master Brick: Connected via USB.</li><li>Temperature Bricklet: Connected to one of the ports of the master brick.</li></ul><h4 id="connect-the-master-brick-to-the-computer">Connect the master brick to the computer.</h4><p>After connecting the master brick to the computer using a USB cable, it should boot up, as indicated by the LEDs lighting up.</p><h3 id="installing-the-tinkerforge-java-api">Installing the TinkerForge Java API</h3><p>To interact with TinkerForge hardware via Java, you need the TinkerForge Java API. This example uses Maven to manage the dependencies. The coordinates for the TinkerForge dependencies are:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>com.tinkerforge<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>tinkerforge<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;version&gt;</span>2.1.33<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><h3 id="writing-the-first-java-program">Writing the first Java program</h3><p>The following example shows a simple Java program that reads temperature data from the Temperature Bricklet. For this purpose, a Java class called “<strong>TemperatureReader</strong> ` created. The following imports are necessary for this program. Typically, the IDE should automatically display the relevant suggestions.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.tinkerforge.BrickletTemperature</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.tinkerforge.IPConnection</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.tinkerforge.TinkerforgeException</span><span class="p">;</span></span></span></code></pre></div></div><p>The temperature bracket&rsquo;s UID is required below. The UID is usually printed on the bricklet or can be found via the TinkerForge Brick Viewer.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">TemperatureReader</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">HOST</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"localhost"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">PORT</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">4223</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Replace XYZ with your Bricklet UID</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">TEMPERATURE_BRICKLET_UID</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"XYZ"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>For simplicity, the actual program is implemented directly in the main method of the class.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">IPConnection</span><span class="w"/><span class="n">ipcon</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IPConnection</span><span class="p">();</span><span class="w"/><span class="c1">// Create IP connection</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create device object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">BrickletTemperature</span><span class="w"/><span class="n">temp</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">BrickletTemperature</span><span class="p">(</span><span class="n">TEMPERATURE_BRICKLET_UID</span><span class="p">,</span><span class="w"/><span class="n">ipcon</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">ipcon</span><span class="p">.</span><span class="na">connect</span><span class="p">(</span><span class="n">HOST</span><span class="p">,</span><span class="w"/><span class="n">PORT</span><span class="p">);</span><span class="w"/><span class="c1">// Connect to brickd</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Connected to TinkerForge"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Read temperature</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">short</span><span class="w"/><span class="n">temperature</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">temp</span><span class="p">.</span><span class="na">getTemperature</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Temperature: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">temperature</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">100</span><span class="p">.</span><span class="na">0</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" °C"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">ipcon</span><span class="p">.</span><span class="na">disconnect</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">TinkerforgeException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">err</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Error: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><em>note</em> : Replace “XYZ” with the actual UID of the temperature bricklet.</p><p><strong>IP connection</strong> : Manages the connection to the TinkerForge daemon (<code>**brickd**</code>), which facilitates communication between the computer and the bricks/bricklets.</p><p><strong>BrickletTemperature</strong> : Represents the temperature bricklet element and allows interaction with its functions.</p><p><strong>getTemperature():</strong> This function gets the current temperature in hundredths of a degree Celsius. The value is converted to degrees Celsius by dividing by 100.0.</p><h4 id="running-and-testing-the-program">Running and testing the program</h4><p><strong>Start the TinkerForge daemon (“brickd”).</strong></p><p>Before running the Java program, make sure the TinkerForge daemon is running. This daemon manages communication between the computer and the TinkerForge hardware.</p><p><strong>Running the Java program</strong></p><p>After starting the method<strong>TemperatureReader.main()</strong> &rsquo;&rsquo; you can see the following output on the console.</p><div class="pull-quote"><p><strong>Connected to TinkerForge</strong></p><p><strong>Temperature: 23,45 °C</strong></p></div><p>Of course, the temperature value varies depending on the environment.</p><h4 id="dealing-with-errors">Dealing with errors</h4><p>If errors occur, note the following:</p><p><strong>Incorrect UID</strong> : Make sure the UID in the source code matches the UID of the Temperature Bricklet.</p><p><strong>Daemon is not running</strong> : Check if the<code>**brickd**</code> is running and connected to the master brick.</p><p><strong>Connection problems</strong> : Check the USB connections and ensure the master brick is properly connected to the computer.</p><h4 id="event-driven-programming">Event-driven programming</h4><p>Now that a simple Java program for reading temperature data has been created, advanced features can be explored. Instead of actively querying temperature data, callbacks can be set up to receive data asynchronously.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.tinkerforge.BrickletTemperature</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.tinkerforge.IPConnection</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.tinkerforge.TinkerforgeException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">TemperatureCallback</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">HOST</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"localhost"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">PORT</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">4223</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">TEMPERATURE_BRICKLET_UID</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"XYZ"</span><span class="p">;</span><span class="w"/><span class="c1">// Replace XYZ with your Bricklet UID</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">IPConnection</span><span class="w"/><span class="n">ipcon</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IPConnection</span><span class="p">();</span><span class="w"/><span class="c1">// Create IP connection</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create device object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">BrickletTemperature</span><span class="w"/><span class="n">temp</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">BrickletTemperature</span><span class="p">(</span><span class="n">TEMPERATURE_BRICKLET_UID</span><span class="p">,</span><span class="w"/><span class="n">ipcon</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">ipcon</span><span class="p">.</span><span class="na">connect</span><span class="p">(</span><span class="n">HOST</span><span class="p">,</span><span class="w"/><span class="n">PORT</span><span class="p">);</span><span class="w"/><span class="c1">// Connect to brickd</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Connected to TinkerForge"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Register temperature callback</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">temp</span><span class="p">.</span><span class="na">addTemperatureListener</span><span class="p">((</span><span class="n">temperature</span><span class="p">)</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Temperature Callback: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">temperature</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">100</span><span class="p">.</span><span class="na">0</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" °C"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Set callback period to 1 second (1000 ms)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">temp</span><span class="p">.</span><span class="na">setTemperatureCallbackPeriod</span><span class="p">(</span><span class="n">1000</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Keep the program running</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Press Ctrl+C to exit."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">while</span><span class="w"/><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">1000</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">TinkerforgeException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">err</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Error: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Explanation:</p><p><strong>addTemperatureListener</strong> : Registers a callback function that fires every time the temperature is updated.</p><p><strong>setTemperatureCallbackPeriod</strong> : Defines the interval (in milliseconds) at which the callback is invoked.</p><h4 id="controlling-actors">Controlling actors</h4><p>If actuators such as LEDs or motors have been connected via Bricklets, they can be controlled programmatically.</p><p><strong>Example: Controlling an LED bricklet</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.tinkerforge.BrickletLED</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.tinkerforge.IPConnection</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.tinkerforge.TinkerforgeException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">LEDControl</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">HOST</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"localhost"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">PORT</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">4223</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Replace ABC with your LED Bricklet UID</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">LED_BRICKLET_UID</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"ABC"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">IPConnection</span><span class="w"/><span class="n">ipcon</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IPConnection</span><span class="p">();</span><span class="w"/><span class="c1">// Create IP connection</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">BrickletLED</span><span class="w"/><span class="n">led</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">BrickletLED</span><span class="p">(</span><span class="n">LED_BRICKLET_UID</span><span class="p">,</span><span class="w"/><span class="n">ipcon</span><span class="p">);</span><span class="w"/><span class="c1">// Create device object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">ipcon</span><span class="p">.</span><span class="na">connect</span><span class="p">(</span><span class="n">HOST</span><span class="p">,</span><span class="w"/><span class="n">PORT</span><span class="p">);</span><span class="w"/><span class="c1">// Connect to brickd</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Connected to TinkerForge"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Turn LED on</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">led</span><span class="p">.</span><span class="na">setColorLEDValue</span><span class="p">(</span><span class="s">"on"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"LED is ON"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Wait for 2 seconds</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Thread</span><span class="p">.</span><span class="na">sleep</span><span class="p">(</span><span class="n">2000</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Turn LED off</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">led</span><span class="p">.</span><span class="na">setColorLEDValue</span><span class="p">(</span><span class="s">"off"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"LED is OFF"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">ipcon</span><span class="p">.</span><span class="na">disconnect</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">TinkerforgeException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">InterruptedException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">err</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Error: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="conclusion">Conclusion</h3><p>Programming with Java on the TinkerForge platform provides a powerful way to create interactive and intelligent electronic projects. There are hardly any limits to your imagination here. Thanks to the connection to Java, you can link your projects with many other technologies, which dramatically simplifies the integration of existing infrastructures and projects.</p><p>Happy coding and creating!</p>
]]></content:encoded><category>Java</category><category>TinkerForge</category><media:content url="https://svenruppert.com/images/2024/09/IMG_0507.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/09/IMG_0507.jpg"/><enclosure url="https://svenruppert.com/images/2024/09/IMG_0507.jpg" type="image/jpeg" length="0"/></item><item><title>Building More Complex Apps with Flow</title><link>https://svenruppert.com/posts/building-more-complex-apps-with-flow/</link><pubDate>Thu, 22 Aug 2024 12:20:26 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/building-more-complex-apps-with-flow/</guid><description>In this part of the series about Vaadin Flow, I will show how I can create the basic framework for the graphic design of a work application. The focus here is on the design of the work area and the organisation of the individual logical application groups. In other words, we create the application layout that can be used for an industrial project.</description><content:encoded>&lt;![CDATA[<p>In this part of the series about Vaadin Flow, I will show how I can create the basic framework for the graphic design of a work application. The focus here is on the design of the work area and the organisation of the individual logical application groups. In other words, we create the application layout that can be used for an industrial project.</p><p>Of course, this also includes dealing with different languages ​​based on i18N.</p><p>One of the first steps in an application is often to think about how everything should be presented on the screen. If menus are used, headers and footers should be used for elements, and what about the basic design of the workspace? The questions here are to be understood as examples, as they will, of course, always depend on the context and the target group.</p><ol><li><a href="https://svenruppert.com/2024/08/22/auto-draft/#applayout">AppLayout</a></li><li><a href="https://svenruppert.com/2024/08/22/auto-draft/#what-is-i18n-internationalization-in-java">What is i18N - Internationalization in Java</a></li><li><a href="https://svenruppert.com/2024/08/22/auto-draft/#i18n-in-vaadin-flow">i18N in Vaadin Flow</a></li><li><a href="https://svenruppert.com/2024/08/22/auto-draft/#conclusion">Conclusion</a></li></ol><h2 id="applayout">AppLayout</h2><p>AppLayout is a reasonably robust and easy-to-use solution that comes with the Vaadin Flow framework. This class provides the entry point for quickly building an application&rsquo;s framework. We looked at this class briefly in the first part and will now go into more detail here.</p><p>A classic<strong>Applayout</strong> consists of a header, where part of the navigation is usually housed. There is also a corresponding footer, which is usually used for lower-priority status messages. The middle is then divided into one to three parts. We will choose a classic two-column design here. The left side is the main navigation, the detailed navigation of which will then be visible in the header like a menu bar.</p><p>The layout consists of three sections: a horizontal navigation bar (called Navbar), a fold-out navigation bar (drawer), and a content area. An application&rsquo;s main navigation blocks should be positioned in the navigation bar and/or drawer while rendering views in the content area.</p><p>The app layout is responsive and automatically adapts to the screen size of desktops, tablets, and mobile devices. This behaviour saves us a lot of customisation work that we would otherwise have to implement ourselves.</p><p>The navigation structure depends mainly on the number of elements to be shown and whether a sub-navigation is required.</p><p>The navigation bar is a good choice for multiple items (3-5) as they fit in the viewport without scrolling. If you need to display more items or small screen support is a priority, the Drawer is a better choice. It can accommodate a longer list of links without scrolling and collapses into a hamburger menu on small screens. Additionally, a vertical list of items is easier for the user to scan. If multi-level or hierarchical navigation is required, Draver is the first level. The secondary (and tertiary) navigation elements can be placed using the drawer or in the navigation bar (Navbar).</p><p>The implementation can then look like this.</p><p>We create the basic framework in<strong>MainLayout</strong> from the class<strong>AppLayout</strong> is derived. Here, we have to use the methods for setting the individual navigation areas. Let&rsquo;s start by building the main navigation, which is on the left side in this example. Here you will see a combination of icon and descriptions for the main areas of the application. The necessary graphical elements are displayed along with the application title via the method<strong>addToDrawer(&hellip;)</strong> set.</p><p>The application title is an instance of the class<strong>H1</strong>. Here the text to be displayed is simply selected using the method<strong>setText()</strong> handover.</p><p>The navigation elements are created using the class<strong>SideNavItem</strong> realised. The transfer parameters are a label. a target view and an icon. All<strong>SideNavItem</strong> - Instances are in the instance of the class<strong>SideNav</strong> summarised and transferred to the layout. In case the number of entries is larger and you want the user to be able to scroll through this list, the instance of the class<strong>SideNav</strong> is embedded into an instance of the class<strong>Scroller</strong>.</p><p>In the source code, it looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Scroller</span><span class="w"/><span class="nf">primaryNavigation</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">SideNav</span><span class="w"/><span class="n">sideNav</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SideNav</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">sideNav</span><span class="p">.</span><span class="na">addItem</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s">"Dashboard"</span><span class="p">,</span><span class="w"/><span class="n">DashboardView</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">DASHBOARD</span><span class="p">.</span><span class="na">create</span><span class="p">()),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s">"Orders"</span><span class="p">,</span><span class="w"/><span class="n">AllOrdersView</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CART</span><span class="p">.</span><span class="na">create</span><span class="p">()),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s">"Customers"</span><span class="p">,</span><span class="w"/><span class="n">CustomersView</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">USER_HEART</span><span class="p">.</span><span class="na">create</span><span class="p">()),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s">"Products"</span><span class="p">,</span><span class="w"/><span class="n">ProductsView</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">PACKAGE</span><span class="p">.</span><span class="na">create</span><span class="p">()),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s">"Documents"</span><span class="p">,</span><span class="w"/><span class="n">DocumentsView</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">RECORDS</span><span class="p">.</span><span class="na">create</span><span class="p">()),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s">"Tasks"</span><span class="p">,</span><span class="w"/><span class="n">TasksView</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">LIST</span><span class="p">.</span><span class="na">create</span><span class="p">()),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s">"Analytics"</span><span class="p">,</span><span class="w"/><span class="n">AnalyticsView</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">VaadinIcon</span><span class="p">.</span><span class="na">CHART</span><span class="p">.</span><span class="na">create</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Scroller</span><span class="w"/><span class="n">scroller</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Scroller</span><span class="p">(</span><span class="n">pageNav</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">scroller</span><span class="p">.</span><span class="na">setClassName</span><span class="p">(</span><span class="n">LumoUtility</span><span class="p">.</span><span class="na">Padding</span><span class="p">.</span><span class="na">SMALL</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"/><span class="n">scroller</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span></span></span></code></pre></div></div><p>To complete the layout, we will put all the elements together.</p><p>This is implemented in the constructor.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">H1</span><span class="w"/><span class="n">appTitle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">H1</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">public</span><span class="w"/><span class="nf">MainLayout</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">addToDrawer</span><span class="p">(</span><span class="n">appTitle</span><span class="p">,</span><span class="w"/><span class="n">primaryNavigation</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">setPrimarySection</span><span class="p">(</span><span class="n">Section</span><span class="p">.</span><span class="na">DRAWER</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span></span></span></code></pre></div></div><p>In this example, you can see that the labels are all stored directly in the source code. We will change this later so that we can make the application multilingual. I ask for a bit of patience here. We also see the specification of the targets using the class name. We&rsquo;ll look at that a little later, too.</p><p>In the previous post, I showed how the MainLayout class is used.</p><figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXexNjpynByP-nsgv2Yjs5H2mgGom5bBIJF3TNVg_3AeqUVC5qjfQ-TmNhUL_Y_SsRJWXwb_2kwcJBtnsSq1A8xa8PFgUEGR4-DLK1bT1tJP9xoiSDH_J84HXhrq2LDN2-zqlrwuwE3qU3hfygZWm0eWqksU?key=9_QRfZjnqzVTURFvNMzCHw" alt="" loading="lazy" decoding="async"/><p><em>Screen with the created menu items</em></p><p>If you want to create a sub-navigation, you can make additional menu entries in the header on the right. This is often used when you want to divide a somewhat more complex process into different views. For example, let&rsquo;s take the illustration for dealing with orders. Let’s just assume that there are the sub-areas “<strong>All orders</strong> ”, “<strong>Open orders</strong> ”, “<strong>Completed orders</strong> " and &ldquo;<strong>Abandoned orders</strong> ” must give. This results in another four navigation destinations, which are, however, summarised under the item Orders.</p><figure><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfW_oA6KvWOGVClV8D5w_yhltkKoWISvGYbe-JOfxqwQYxEKTMxoCL9J-ewrbht-9E0hWVEMyh17K2u6J3Pie6a4h6BAW8uVGml1kT0tU2ePkvhIXYxHjVVcpAeByqpqV9g-Uvlv_itBa0dEhKRMwD0rQ8?key=9_QRfZjnqzVTURFvNMzCHw" alt="" loading="lazy" decoding="async"/><p><em>Screenshot after navigating to the Orders menu item</em></p><p>These submenu items are the same for all views that deal with orders. This immediately raises the question of how to extract this menu. Great mechanisms from inheritance come into play here. We create a class<strong>AbstractHeaderView</strong> and<strong>AbstractOrdersView.</strong> All constructs required for a generic implementation of secondary navigation are stored in the first class mentioned. This includes, for example, how the second navigation bar should be positioned, what graphic properties it has and whether there is a hamburger symbol that can be used to show or hide the main navigation.</p><p>Ultimately, all that needs to be added is which navigation points must be created for each implementation. The respective implementation of the method<strong>secondaryNavigationsLinks()</strong> defines these menu items.</p><p>The following listing shows the implementation for the screenshot shown, divided into the generic part<strong>AbstractHeaderView</strong> and the specific one for the orders<strong>AbstractOrdersView.</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public abstract class AbstractViewHeader</span></span><span class="line"><span class="cl">    extends Composite<span class="nt">&lt;VerticalLayout&gt;</span> {</span></span><span class="line"><span class="cl">  public AbstractViewHeader(String subTitle) {</span></span><span class="line"><span class="cl">    initView(subTitle);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  protected RouterLink createLink(String viewName,</span></span><span class="line"><span class="cl">                                  Class<span class="err">&lt;</span>? extends Composite&gt; viewClass) {</span></span><span class="line"><span class="cl">    RouterLink link = new RouterLink();</span></span><span class="line"><span class="cl">    link.add(viewName);</span></span><span class="line"><span class="cl">    link.setRoute(viewClass);</span></span><span class="line"><span class="cl">    link.addClassNames(LumoUtility.Display.FLEX,</span></span><span class="line"><span class="cl">        LumoUtility.AlignItems.CENTER,</span></span><span class="line"><span class="cl">        LumoUtility.Padding.Horizontal.MEDIUM,</span></span><span class="line"><span class="cl">        LumoUtility.TextColor.SECONDARY,</span></span><span class="line"><span class="cl">        LumoUtility.FontWeight.MEDIUM);</span></span><span class="line"><span class="cl">    link.getStyle().set("text-decoration", "none");</span></span><span class="line"><span class="cl">    return link;</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  public void initView(String subTitle) {</span></span><span class="line"><span class="cl">    DrawerToggle toggle = new DrawerToggle();</span></span><span class="line"><span class="cl">    H2 viewTitle = new H2(getTranslation(subTitle));</span></span><span class="line"><span class="cl">    HorizontalLayout titleBar = new HorizontalLayout(toggle, viewTitle);</span></span><span class="line"><span class="cl">    titleBar.setAlignItems(FlexComponent.Alignment.CENTER);</span></span><span class="line"><span class="cl">    titleBar.setSpacing(false);</span></span><span class="line"><span class="cl">    VerticalLayout viewHeader = getContent();</span></span><span class="line"><span class="cl">    viewHeader.add(titleBar, secondaryNavigation());</span></span><span class="line"><span class="cl">    viewHeader.setPadding(false);</span></span><span class="line"><span class="cl">    viewHeader.setSpacing(false);</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  private HorizontalLayout secondaryNavigation() {</span></span><span class="line"><span class="cl">    HorizontalLayout navigation = new HorizontalLayout();</span></span><span class="line"><span class="cl">    navigation.addClassNames(</span></span><span class="line"><span class="cl">        LumoUtility.JustifyContent.CENTER,</span></span><span class="line"><span class="cl">        LumoUtility.Gap.SMALL,</span></span><span class="line"><span class="cl">        LumoUtility.Height.MEDIUM);</span></span><span class="line"><span class="cl">    secondaryNavigationLinks().forEach(navigation::add);</span></span><span class="line"><span class="cl">    return navigation;</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  public abstract List<span class="nt">&lt;RouterLink&gt;</span> secondaryNavigationLinks();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">public abstract class AbstractOrdersView</span></span><span class="line"><span class="cl">    extends AbstractView<span class="nt">&lt;HorizontalLayout&gt;</span></span></span><span class="line"><span class="cl">    implements LocaleChangeObserver {</span></span><span class="line"><span class="cl">  public static final String CANCELLED = "orders.subnavigation.cancelled";</span></span><span class="line"><span class="cl">  public static final String COMPLETED = "orders.subnavigation.completed";</span></span><span class="line"><span class="cl">  public static final String OPEN = "orders.subnavigation.open";</span></span><span class="line"><span class="cl">  public static final String ALL = "orders.subnavigation.all";</span></span><span class="line"><span class="cl">  private final String subTitle;</span></span><span class="line"><span class="cl">  public AbstractOrdersView(String subTitle) {</span></span><span class="line"><span class="cl">    this.subTitle = subTitle;</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">  @Override</span></span><span class="line"><span class="cl">  protected AbstractViewHeader createViewHeader() {</span></span><span class="line"><span class="cl">    return new AbstractViewHeader(subTitle) {</span></span><span class="line"><span class="cl">      @Override</span></span><span class="line"><span class="cl">      public List<span class="nt">&lt;RouterLink&gt;</span> secondaryNavigationLinks() {</span></span><span class="line"><span class="cl">        return List.of(</span></span><span class="line"><span class="cl">            createLink(getTranslation(ALL), AllOrdersView.class), </span></span><span class="line"><span class="cl">            createLink(getTranslation(OPEN), OpenOrdersView.class), </span></span><span class="line"><span class="cl">            createLink(getTranslation(COMPLETED), CompletedOrdersView.class), </span></span><span class="line"><span class="cl">            createLink(getTranslation(CANCELLED), CancelledOrdersView.class));</span></span><span class="line"><span class="cl">      }</span></span><span class="line"><span class="cl">    };</span></span><span class="line"><span class="cl">  }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Here, you can clearly see that the source text is greatly reduced when there are a large number of navigation elements. A minimal basic framework is created to implement the variations for the individual views. I have intentionally refrained from including functions for processing orders in this example because I want to focus on the structure here.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"allOrders"</span><span class="p">,</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainLayout</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@PreserveOnRefresh</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">AllOrdersView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">AbstractOrdersView</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">SUB_TITLE</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"orders.all.subtitle"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">public</span><span class="w"/><span class="nf">AllOrdersView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">super</span><span class="p">(</span><span class="n">SUB_TITLE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="c1">//the necessary elements are placed here</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">getContent</span><span class="p">().</span><span class="na">add</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">(</span><span class="s">"TBD ALL Orders"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">localeChange</span><span class="p">(</span><span class="n">LocaleChangeEvent</span><span class="w"/><span class="n">event</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">// fuer i18N</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>With the basic framework shown here, you can build quite complex navigations. Now, let&rsquo;s get to the topic of i18n. A web application usually needs to support multiple languages. I will not discuss languages ​​here that require a change in reading direction. In this example, I&rsquo;ll focus on supporting any number of languages ​​with a left-to-right reading order. Examples of languages ​​here will be German and English. The primary function here is quite simple</p><h2 id="what-is-i18n---internationalization-in-java">What is i18N - Internationalization in Java</h2><p>The basic concept of<strong>i18n (internationalisation)</strong> in Java is to design and develop an application that can be easily adapted to different languages ​​and regions without requiring significant changes to the code.</p><p><strong>Resource bundle</strong></p><p>A resource bundle is a set of key-value pairs that store country-specific data, such as B. Strings for messages, labels and other UI components. In Java, resource bundles are usually property files named after the locale they represent (e.g. &ldquo;<strong>messages_en_US.properties</strong> " for US English, &ldquo;<strong>messages_de.properties</strong> “ for general German).</p><p>When the application runs, it dynamically loads the appropriate resource bundle based on the user&rsquo;s locale. This allows the application to display content in the user&rsquo;s preferred language without hard-coding the text in the source code.</p><p><strong>Locale</strong></p><p>A<code>locale</code> in Java is an object that represents a specific geographical, political or cultural region. It includes language, country and sometimes variant codes. For example,<code>Locale.ENGLISH</code> represents the English language and<code>Locale.US</code> represents the United States.</p><p>The class<code>**Local**</code> customises information for the user, such as B. Text, dates, numbers and currencies, according to its region.</p><p><strong>Message Formatting</strong></p><p>Java provides classes like<code>**MessageFormat**</code> to handle complex message formatting that allows dynamic insertion of values ​​into localised messages (e.g. &ldquo;Welcome, {0}!&rdquo;, where<code>{0}</code> can be replaced with the user&rsquo;s name).</p><p>You define message templates in your resource bundles, and at runtime, the class replaces<code>**MessageFormat**</code> Placeholders with actual values ​​to ensure messages are appropriately localised and formatted.</p><p><strong>Date, number and currency formatting</strong></p><p>Dates, numbers, and currencies are often represented differently in different locales (e.g.<code>MM/DD/YYYY</code> vs.<code>DD/MM/YYYY</code> for dates). Java provides classes like<code>**DateFormat**</code>,<code>**NumberFormat**</code> and<code>**Currency**</code> to process these deviations.</p><p>These classes format data according to locale settings and ensure users see information in the expected format.</p><p><strong>Fallback mechanism</strong></p><p>If a specific locale&rsquo;s resource package is unavailable, Java uses a fallback mechanism to select a more general package (e.g., falling back from<code>messages_fr_CA.properties</code> to<code>messages_fr.properties</code>).</p><p>This ensures that the application remains functional even if a full set of localised resources is unavailable for each locale.</p><p>The basic concept of i18n in Java is to create applications that can support multiple languages ​​and regional settings with minimal changes to the code base. This includes using resource bundles, locale-dependent classes for formatting, and a fallback mechanism to ensure a seamless user experience across different locales.</p><h2 id="i18n-in-vaadin-flow">i18N in Vaadin Flow</h2><p>To use i18N, the application only needs to have the corresponding text files with the keys and contents available in the classpath under the vaadin-i18n directory with the filename prefix translations (e.g. src/main/resources/vaadin-i18n/translations.properties).</p><p>When a translation is requested or when the I18NProvider is used for the first time, the vaadin-i18n directory is checked to see if it contains any translations.properties or translations_[langcode].properties files. All language codes are collected from the available files and added as provided locales in the DefaultI18NProvider.</p><p>The translations.properties file is a standard translation file used for any locale that does not have a specific translation file. For example, locale translation files are called translations_de.properties or translations_en.properties. Automatic locale creation supports one to three parts (e.g. translations_language_country_variant.properties).</p><p>The locale to use is determined by matching the locales provided by the I18NProvider with the Accept-Language header in the client&rsquo;s initial response. It will be used if an exact match (i.e., language and country) is found. Otherwise, it will just try to find a match by language. If neither is found, the locale defaults to the first &ldquo;supported&rdquo; locale from<strong>I18NProvider.getProvidedLocales()</strong> set. If this is empty, will<strong>Locale.getDefault()</strong> used.</p><p>Implementing internationalisation in an application is a combination of using<strong>I18NProvider</strong> and updating translations when locale changes. This can be done, for example, by the user if he wants to change the language. To simplify this, the application classes that control the localised captions and text can<strong>LocaleChangeObserver</strong> implement to receive events related to locale changes. During navigation, this observer will also be notified when the component is attached, but before<strong>onAttach()</strong> is called. All URL parameters from navigation are set so that they can be used to determine status.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">LocaleObserver</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">Div</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">LocaleChangeObserver</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">localeChange</span><span class="p">(</span><span class="n">LocaleChangeEvent</span><span class="w"/><span class="n">event</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">setText</span><span class="p">(</span><span class="n">getTranslation</span><span class="p">(</span><span class="s">"my.translation"</span><span class="p">,</span><span class="w"/><span class="n">getUserId</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>If you want to set the texts explicitly, you can use this method:<strong>getTranslation(..)</strong>.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MyLocale</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">Div</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">MyLocale</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">setText</span><span class="p">(</span><span class="n">getTranslation</span><span class="p">(</span><span class="s">"my.translation"</span><span class="p">,</span><span class="w"/><span class="n">getUserId</span><span class="p">()));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example project, the listing for creating the main menu entries is rewritten. For clarity, here are both versions for a menu entry.</p><p><strong>High Version</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="s2">"Orders"</span><span class="p">,</span><span class="w"/><span class="n">AllOrdersView</span><span class="p">.</span><span class="k">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">VaadinIcon</span><span class="p">.</span><span class="n">CART</span><span class="p">.</span><span class="k">create</span><span class="p">())</span></span></span></code></pre></div></div><p><strong>New version</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">MENU_ITEM_ORDERS</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"mainlayout.menuitem.orders"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">new</span><span class="w"/><span class="n">SideNavItem</span><span class="p">(</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">getTranslation</span><span class="p">(</span><span class="n">MENU_ITEM_ORDERS</span><span class="p">),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">AllOrdersView</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">CART</span><span class="p">.</span><span class="na">create</span><span class="p">())</span></span></span></code></pre></div></div><p><strong>translation.properties</strong> :</p><p>mainlayout.menuitem.orders=Orders</p><h2 id="conclusion">Conclusion</h2><p>With these two elements, the ApplLayout and the realisation or implementation of i18N, two fundamental building blocks for developing a web application have been laid. From here, the application can now be built step by step. We will deal with the individual topics in the next parts, so it remains exciting.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2024/08/1BE8F793-F251-41B9-95B6-52D258A32EC9.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/08/1BE8F793-F251-41B9-95B6-52D258A32EC9.jpg"/><enclosure url="https://svenruppert.com/images/2024/08/1BE8F793-F251-41B9-95B6-52D258A32EC9.jpg" type="image/jpeg" length="0"/></item><item><title>CWE-377 - Insecure Temporary File in Java</title><link>https://svenruppert.com/posts/cwe-377-insecure-temporary-file-in-java/</link><pubDate>Wed, 21 Aug 2024 13:17:12 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/cwe-377-insecure-temporary-file-in-java/</guid><description>In software development, temporary files are often used to store data temporarily during an application’s execution. These files may contain sensitive information or be used to hold data that must be processed or passed between different parts of a program. However, if these temporary files are not managed securely, they can introduce vulnerabilities that may compromise the application&rsquo;s confidentiality, integrity, or availability. The Common Weakness Enumeration (CWE) identified CWE-377 as a weakness associated with the insecure creation and management of temporary files.</description><content:encoded>&lt;![CDATA[<p>In software development, temporary files are often used to store data temporarily during an application’s execution. These files may contain sensitive information or be used to hold data that must be processed or passed between different parts of a program. However, if these temporary files are not managed securely, they can introduce vulnerabilities that may compromise the application&rsquo;s confidentiality, integrity, or availability. The Common Weakness Enumeration (CWE) identified CWE-377 as a weakness associated with the insecure creation and management of temporary files.</p><ol><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#understanding-cwe-377">Understanding CWE-377</a><ol><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#cwe-377-in-java">CWE-377 in Java</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#example-of-vulnerable-code">Example of Vulnerable Code</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#potential-impacts">Potential Impacts</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#mitigations">Mitigations</a><ol><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#use-file-createtempfile-properly">Use<code>File.createTempFile()</code> Properly</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#ensure-proper-file-permissions">Ensure Proper File Permissions</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#avoid-hardcoded-file-names">Avoid Hardcoded File Names</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#handle-temporary-files-in-a-privileged-context">Handle Temporary Files in a Privileged Context</a></li></ol></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#advanced-considerations">Advanced Considerations</a><ol><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#use-java-nio-file-package">Use<code>java.nio.file</code> Package</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#consider-using-in-memory-solutions">Consider Using In-Memory Solutions</a></li></ol></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#best-practices">Best Practices</a></li></ol></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#opensource-libraries-for-secure-temp-file-handling">OpenSource Libraries for secure temp file handling</a><ol><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#apache-commons-io">Apache Commons IO</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#google-guava">Google Guava</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#junit-5-junit-jupiter">JUnit 5 (JUnit Jupiter)</a></li></ol></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#practical-examples">Practical examples</a><ol><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#restful-api-demo-upload-with-javalin">RESTful API Demo - Upload with Javalin</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#insecure-implementation">Insecure Implementation</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#secure-implementation">Secure Implementation</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#key-differences">Key Differences</a></li><li><a href="https://svenruppert.com/2024/08/21/auto-draft/#conclusion-on-cwe-377-insecure-temporary-file">Conclusion on CWE-377: Insecure Temporary File</a></li></ol></li></ol><h2 id="understanding-cwe-377">Understanding CWE-377</h2><p><strong>CWE-377: Insecure Temporary File</strong> refers to a security weakness that occurs when a program creates a temporary file in an insecure manner. Attackers can exploit this vulnerability to perform various malicious activities, including data tampering, unauthorised data access, or denial of service. Insecure temporary file creation typically involves issues such as:</p><p><strong>Predictable file names</strong> : If temporary files have predictable names, attackers can guess the file names and either read or modify the file contents.</p><p><strong>Insecure file permissions</strong> : Incorrect permissions can allow unauthorised users to access or modify temporary files.</p><p><strong>Race conditions</strong> : A time-of-check to time-of-use (<strong>TOCTOU</strong>) race condition may occur if an attacker creates a file with the same name as the one the application intends to generate before the application does.</p><h3 id="cwe-377-in-java">CWE-377 in Java</h3><p>In Java, developers often use temporary files for various purposes, such as caching, processing intermediate data, or storing temporary results. Java provides several ways to create temporary files, such as the<code>**File.createTempFile()**</code> method, which generates a temporary file with a unique name in the default temporary-file directory. However, improper use of these APIs can lead to CWE-377.</p><h3 id="example-of-vulnerable-code">Example of Vulnerable Code</h3><p>Consider the following example:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">InsecureTempFileExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">File</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"/tmp/tempfile.txt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">tempFile</span><span class="p">.</span><span class="na">createNewFile</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Temporary file created at: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">tempFile</span><span class="p">.</span><span class="na">getAbsolutePath</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the code creates a temporary file with a hardcoded file name (<code>tempfile.txt</code>) in the<code>/tmp</code> directory. This approach is insecure for several reasons: The file name is predictable, allowing an attacker to create a file with the same name before the application does, leading to a TOCTOU race condition. The file is created without any control over file permissions, potentially exposing sensitive data.</p><h3 id="potential-impacts">Potential Impacts</h3><p>CWE-377 can have severe consequences depending on how the temporary file is used and the sensitivity of the data it contains. Some potential impacts include:</p><p><strong>Information Disclosure</strong> : If an attacker can predict the name of a temporary file, they may be able to read its contents if the file is not adequately protected. This can lead to disclosing sensitive information, such as passwords, tokens, or personal data.</p><p><strong>Data Tampering</strong> : An attacker could create or modify a temporary file before the application uses it, resulting in data corruption or unauthorised modifications. This could lead to incorrect application behaviour or the introduction of malicious data into the system.</p><p><strong>Denial of Service (DoS)</strong> : By preemptively creating temporary files with the names an application expects to use, an attacker can prevent the application from functioning correctly, leading to a denial of service.</p><h3 id="mitigations">Mitigations</h3><p>To prevent CWE-377, developers must adhere to secure coding practices when working with temporary files. Below are several mitigation strategies for securely creating and managing temporary files in Java:</p><h4 id="use-filecreatetempfile-properly">Use<code>File.createTempFile()</code> Properly</h4><p>The<code>File.createTempFile()</code> method generates a unique temporary file name, reducing the risk of predictable file names. It also allows developers to specify a directory for the file, though it defaults to the system&rsquo;s temporary-file directory.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SecureTempFileExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">File</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">File</span><span class="p">.</span><span class="na">createTempFile</span><span class="p">(</span><span class="s">"tempfile_"</span><span class="p">,</span><span class="w"/><span class="s">".tmp"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">tempFile</span><span class="p">.</span><span class="na">deleteOnExit</span><span class="p">();</span><span class="w"/><span class="c1">// Ensures the file is deleted when the JVM exits</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Temporary file created at: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">tempFile</span><span class="p">.</span><span class="na">getAbsolutePath</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This approach mitigates several risks:</p><ul><li>The file name is generated randomly, making it difficult for an attacker to predict.</li><li>The file is automatically deleted when the Java Virtual Machine (JVM) exits, reducing the likelihood of stale files.</li></ul><h4 id="ensure-proper-file-permissions">Ensure Proper File Permissions</h4><p>When creating temporary files, setting appropriate file permissions is crucial to prevent unauthorised access. In Java, you can use the<code>**setReadable()**</code>,<code>**setWritable()**</code>, and<code>**setExecutable()**</code> methods to control file permissions.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SecureTempFileWithPermissionsExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">File</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">File</span><span class="p">.</span><span class="na">createTempFile</span><span class="p">(</span><span class="s">"secure_tempfile_"</span><span class="p">,</span><span class="w"/><span class="s">".tmp"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">tempFile</span><span class="p">.</span><span class="na">setReadable</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">tempFile</span><span class="p">.</span><span class="na">setWritable</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">tempFile</span><span class="p">.</span><span class="na">setExecutable</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">tempFile</span><span class="p">.</span><span class="na">deleteOnExit</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Temporary file created with secure permissions at: "</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="n">tempFile</span><span class="p">.</span><span class="na">getAbsolutePath</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the file is readable and writable only by the owner, minimising the risk of unauthorised access.</p><h4 id="avoid-hardcoded-file-names">Avoid Hardcoded File Names</h4><p>Using hardcoded file names, as seen in the initial insecure example, is risky because it makes the temporary file name predictable. Always use mechanisms that generate unique, unpredictable file names, such as<code>**File.createTempFile()**</code>.</p><h4 id="handle-temporary-files-in-a-privileged-context">Handle Temporary Files in a Privileged Context</h4><p>Managing temporary files in a more controlled or privileged environment may be beneficial when dealing with sensitive data. This could involve creating the files in a directory with restricted access or using Java’s<code>**AccessController**</code> to enforce stricter security policies around file operations.</p><h3 id="advanced-considerations">Advanced Considerations</h3><h4 id="use-javaniofile-package">Use<code>java.nio.file</code> Package</h4><p>The<code>**java.nio.file**</code> package, introduced in Java 7, provides more robust and flexible mechanisms for file handling, including temporary file creation. The<code>**Files**</code> class offers the<code>**createTempFile()**</code> method, which can also specify file attributes like permissions.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.nio.file.Files;</span></span><span class="line"><span class="cl">import java.nio.file.Path;</span></span><span class="line"><span class="cl">import java.nio.file.attribute.PosixFilePermissions;</span></span><span class="line"><span class="cl">import java.util.Set;</span></span><span class="line"><span class="cl">public class NioSecureTempFileExample {</span></span><span class="line"><span class="cl">    public static void main(String[] args) throws IOException {</span></span><span class="line"><span class="cl">        Set<span class="nt">&lt;PosixFilePermission&gt;</span> permissions = PosixFilePermissions.fromString("rw-------");</span></span><span class="line"><span class="cl">        Path tempFile = Files.createTempFile("nio_tempfile_", </span></span><span class="line"><span class="cl">                                             ".tmp",</span></span><span class="line"><span class="cl">                                             PosixFilePermissions.asFileAttribute(permissions));</span></span><span class="line"><span class="cl">        System.out.println("Temporary file created with NIO at: " + tempFile.toAbsolutePath());</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This example shows how to set file permissions using the NIO package, providing fine-grained control over file security attributes.</p><h4 id="consider-using-in-memory-solutions">Consider Using In-Memory Solutions</h4><p>Some applications may be able to avoid creating temporary files altogether by using in-memory storage solutions, such as<code>**ByteArrayOutputStream**</code>, for temporary data. This approach eliminates the risks associated with file-system-based temporary storage but may not be suitable for large data sets or applications with significant memory constraints.</p><h3 id="best-practices">Best Practices</h3><p>To ensure secure temporary file handling in Java, developers should adopt the following best practices:</p><p><strong>Prefer Secure Defaults</strong> : Always use methods like<code>**File.createTempFile()**</code> or<code>**Files.createTempFile()**</code> that provide secure defaults for file creation.</p><p><strong>Set File Permissions Explicitly</strong> : Ensure that temporary files have the minimal necessary permissions and avoid granting unnecessary access to other users.</p><p><strong>Avoid Predictable File Names</strong> : Never use hardcoded or predictable names for temporary files. Always generate unique file names using secure APIs.</p><p><strong>Use<code>deleteOnExit()</code></strong> : When possible, use<code>**deleteOnExit()**</code> to ensure that temporary files are cleaned up automatically when the JVM terminates.</p><p><strong>Limit Scope of Temporary Files</strong> : Store temporary files in directories with restricted access, and consider creating a dedicated directory for temporary files that require stricter security controls.</p><p><strong>Handle Exceptions Gracefully</strong> : Always handle exceptions when working with temporary files, ensuring the application remains secure and stable even if file operations fail.</p><p>CWE-377: Insecure Temporary File is a significant security concern in software development, particularly in sensitive data processing environments. In Java, developers must be vigilant in creating, managing, and securing temporary files to avoid introducing vulnerabilities that malicious actors could exploit.</p><p>By following the guidelines and best practices outlined in this document, Java developers can mitigate the risks associated with CWE-377 and ensure that their applications handle temporary files securely. The use of secure APIs, proper file permissions, and cautious file management practices are essential for maintaining the confidentiality, integrity, and availability of data within a Java application.</p><p>Ensuring that temporary files are managed securely is not only a best practice but a critical aspect of developing robust, secure Java applications. By adhering to these principles, developers can protect their applications from common threats and contribute to a safer software ecosystem.</p><h2 id="opensource-libraries-for-secure-temp-file-handling">OpenSource Libraries for secure temp file handling</h2><p>Regarding secure temporary file handling in Java, several open-source libraries can help simplify and enhance security for managing temporary files. Here are some notable ones:</p><h3 id="apache-commons-io">Apache Commons IO</h3><p><strong>Apache Commons IO</strong> provides a utility class for securely creating and managing temporary files.</p><p><strong>Maven Dependency</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>commons-io<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>commons-io<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;version&gt;</span>2.11.0<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><p><strong>Example Usage</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.apache.commons.io.FileUtils</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SecureTempFileExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Create a secure temporary file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">File</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="n">FileUtils</span><span class="p">.</span><span class="na">getTempDirectory</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                        </span><span class="p">.</span><span class="na">createTempFile</span><span class="p">(</span><span class="s">"secureTempFile"</span><span class="p">,</span><span class="w"/><span class="s">".txt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">tempFile</span><span class="p">.</span><span class="na">deleteOnExit</span><span class="p">();</span><span class="w"/><span class="c1">// Ensure the file is deleted on exit</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Optional: Set specific file permissions (POSIX systems)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">tempFile</span><span class="p">.</span><span class="na">exists</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">tempFile</span><span class="p">.</span><span class="na">setReadable</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">tempFile</span><span class="p">.</span><span class="na">setWritable</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">tempFile</span><span class="p">.</span><span class="na">setExecutable</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span><span class="w"/><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Temporary file created at: "</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="o">+</span><span class="w"/><span class="n">tempFile</span><span class="p">.</span><span class="na">getAbsolutePath</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="google-guava">Google Guava</h3><p><strong>Google Guava</strong> also provides utility methods for working with temporary files.</p><p><strong>Maven Dependency</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>com.google.guava<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>guava<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">    <span class="nt">&lt;version&gt;</span>31.0.1-jre<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><p><strong>Example Usage</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">com.google.common.io.Files</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SecureTempFileExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Create a secure temporary file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">File</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">createTempDir</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">tempFile</span><span class="p">.</span><span class="na">deleteOnExit</span><span class="p">();</span><span class="w"/><span class="c1">// Ensure the file is deleted on exit</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Optional: Set specific file permissions (POSIX systems)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">tempFile</span><span class="p">.</span><span class="na">exists</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">tempFile</span><span class="p">.</span><span class="na">setReadable</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">tempFile</span><span class="p">.</span><span class="na">setWritable</span><span class="p">(</span><span class="kc">true</span><span class="p">,</span><span class="w"/><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">tempFile</span><span class="p">.</span><span class="na">setExecutable</span><span class="p">(</span><span class="kc">false</span><span class="p">,</span><span class="w"/><span class="kc">false</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Temporary file created at: "</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="o">+</span><span class="w"/><span class="n">tempFile</span><span class="p">.</span><span class="na">getAbsolutePath</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="junit-5-junit-jupiter">JUnit 5 (JUnit Jupiter)</h3><p><strong>JUnit 5</strong> provides a TempDir extension for securely creating temporary directories and files for testing purposes.</p><p><strong>Maven Dependency</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;groupId&gt;</span>org.junit.jupiter<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;artifactId&gt;</span>junit-jupiter-engine<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">    <span class="nt">&lt;version&gt;</span>5.8.2<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><p><strong>Example Usage</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import org.junit.jupiter.api.Test;</span></span><span class="line"><span class="cl">import org.junit.jupiter.api.io.TempDir;</span></span><span class="line"><span class="cl">import java.io.File;</span></span><span class="line"><span class="cl">import java.io.IOException;</span></span><span class="line"><span class="cl">import java.nio.file.Files;</span></span><span class="line"><span class="cl">import java.nio.file.Path;</span></span><span class="line"><span class="cl">public class SecureTempFileTest {</span></span><span class="line"><span class="cl">    @TempDir</span></span><span class="line"><span class="cl">    Path tempDir;</span></span><span class="line"><span class="cl">    @Test</span></span><span class="line"><span class="cl">    public void testCreateSecureTempFile() throws IOException {</span></span><span class="line"><span class="cl">        // Create a secure temporary file in the provided temp directory</span></span><span class="line"><span class="cl">        Path tempFile = Files.createTempFile(tempDir, </span></span><span class="line"><span class="cl">                                             "secureTempFile", </span></span><span class="line"><span class="cl">                                             ".txt");</span></span><span class="line"><span class="cl">        // Optional: Set specific file permissions (POSIX systems)</span></span><span class="line"><span class="cl">        Set<span class="nt">&lt;PosixFilePermission&gt;</span> permissions </span></span><span class="line"><span class="cl">           = PosixFilePermissions.fromString("rw-------");</span></span><span class="line"><span class="cl">        Files.setPosixFilePermissions(tempFile, permissions);</span></span><span class="line"><span class="cl">        System.out.println("Temporary file created at: " </span></span><span class="line"><span class="cl">            + tempFile.toAbsolutePath());</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Using these libraries can significantly simplify secure temporary file handling in Java applications. Apache Commons IO and Google Guava provide utilities for creating and managing temporary files, while JUnit 5 offers built-in support for creating temporary files and directories during testing.</p><p>Choose the library that best fits your needs and project dependencies. Apache Commons IO and Google Guava are versatile and widely used, whereas JUnit 5&rsquo;s TempDir is excellent for secure temporary file handling in test cases.</p><p>Let&rsquo;s discover some examples.</p><h2 id="practical-examples">Practical examples</h2><h3 id="restful-api-demo---upload-with-javalin">RESTful API Demo - Upload with Javalin</h3><p>Javalin is a lightweight web framework for Java and Kotlin that can be used to create RESTful APIs and web applications. Below, I will provide two examples of handling file uploads and creating temporary files in Javalin: insecure and secure.</p><h3 id="insecure-implementation">Insecure Implementation</h3><p>In this insecure example, we will create temporary files with predictable names without setting appropriate permissions.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">io.javalin.Javalin</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">InsecureTempFileExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Javalin</span><span class="w"/><span class="n">app</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Javalin</span><span class="p">.</span><span class="na">create</span><span class="p">().</span><span class="na">start</span><span class="p">(</span><span class="n">7000</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">app</span><span class="p">.</span><span class="na">post</span><span class="p">(</span><span class="s">"/upload"</span><span class="p">,</span><span class="w"/><span class="n">ctx</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Save uploaded file to a temporary file </span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// with a predictable name</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">File</span><span class="w"/><span class="n">uploadedFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ctx</span><span class="p">.</span><span class="na">uploadedFile</span><span class="p">(</span><span class="s">"file"</span><span class="p">).</span><span class="na">getContent</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">File</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"/tmp/insecure-temp-file.txt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">uploadedFile</span><span class="p">.</span><span class="na">renameTo</span><span class="p">(</span><span class="n">tempFile</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">ctx</span><span class="p">.</span><span class="na">result</span><span class="p">(</span><span class="s">"File uploaded to: "</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w">                    </span><span class="o">+</span><span class="w"/><span class="n">tempFile</span><span class="p">.</span><span class="na">getAbsolutePath</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">ctx</span><span class="p">.</span><span class="na">result</span><span class="p">(</span><span class="s">"File upload failed"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="secure-implementation">Secure Implementation</h3><p>In this secure example, we will use Files.createTempFile to create temporary files with unique, unpredictable names and set appropriate file permissions.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import io.javalin.Javalin;</span></span><span class="line"><span class="cl">import java.io.InputStream;</span></span><span class="line"><span class="cl">import java.nio.file.Files;</span></span><span class="line"><span class="cl">import java.nio.file.Path;</span></span><span class="line"><span class="cl">import java.nio.file.attribute.PosixFilePermission;</span></span><span class="line"><span class="cl">import java.nio.file.attribute.PosixFilePermissions;</span></span><span class="line"><span class="cl">import java.util.Set;</span></span><span class="line"><span class="cl">public class SecureTempFileExample {</span></span><span class="line"><span class="cl">    public static void main(String[] args) {</span></span><span class="line"><span class="cl">        Javalin app = Javalin.create().start(7000);</span></span><span class="line"><span class="cl">        app.post("/upload", ctx -&gt; {</span></span><span class="line"><span class="cl">            // Retrieve uploaded file</span></span><span class="line"><span class="cl">            InputStream uploadedFile </span></span><span class="line"><span class="cl">                = ctx.uploadedFile("file").getContent();</span></span><span class="line"><span class="cl">            // Define the prefix and suffix for the temporary file</span></span><span class="line"><span class="cl">            String prefix = "secureTempFile";</span></span><span class="line"><span class="cl">            String suffix = ".txt";</span></span><span class="line"><span class="cl">            try {</span></span><span class="line"><span class="cl">                // Create temporary file with default permissions</span></span><span class="line"><span class="cl">                Path tempFile = Files.createTempFile(prefix, suffix);</span></span><span class="line"><span class="cl">                // Optional: Set specific file permissions (POSIX systems)</span></span><span class="line"><span class="cl">                Set<span class="nt">&lt;PosixFilePermission&gt;</span> permissions </span></span><span class="line"><span class="cl">                   = PosixFilePermissions.fromString("rw-------");</span></span><span class="line"><span class="cl">                Files.setPosixFilePermissions(tempFile, permissions);</span></span><span class="line"><span class="cl">                // Write uploaded file content to temporary file</span></span><span class="line"><span class="cl">                Files.copy(uploadedFile, tempFile);</span></span><span class="line"><span class="cl">                ctx.result("File uploaded to: " </span></span><span class="line"><span class="cl">                   + tempFile.toAbsolutePath().toString());</span></span><span class="line"><span class="cl">            } catch (IOException e) {</span></span><span class="line"><span class="cl">                ctx.result("File upload failed");</span></span><span class="line"><span class="cl">                e.printStackTrace();</span></span><span class="line"><span class="cl">            }</span></span><span class="line"><span class="cl">        });</span></span><span class="line"><span class="cl">    }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h3 id="key-differences">Key Differences</h3><ol><li><strong>File Creation</strong> :</li></ol><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="w"/><span class="o">*</span><span class="w"/><span class="o">**</span><span class="n">Insecure</span><span class="o">**</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">Uses</span><span class="w"/><span class="n">a</span><span class="w"/><span class="n">predictable</span><span class="w"/><span class="n">file</span><span class="w"/><span class="n">name</span><span class="w"/><span class="p">(</span><span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">insecure</span><span class="o">-</span><span class="n">temp</span><span class="o">-</span><span class="n">file</span><span class="p">.</span><span class="n">txt</span><span class="p">),</span><span class="w"/><span class="n">which</span><span class="w"/><span class="n">can</span><span class="w"/><span class="n">lead</span><span class="w"/><span class="k">to</span><span class="w"/><span class="n">filename</span><span class="w"/><span class="n">collision</span><span class="w"/><span class="k">and</span><span class="w"/><span class="n">unauthorised</span><span class="w"/><span class="k">access</span><span class="p">.</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">*</span><span class="w"/><span class="o">**</span><span class="n">Secure</span><span class="o">**</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">Uses</span><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="n">createTempFile</span><span class="w"/><span class="k">to</span><span class="w"/><span class="k">create</span><span class="w"/><span class="n">a</span><span class="w"/><span class="k">unique</span><span class="p">,</span><span class="w"/><span class="n">unpredictable</span><span class="w"/><span class="n">file</span><span class="w"/><span class="n">name</span><span class="p">.</span></span></span></code></pre></div></div><ol start="2"><li><strong>File Permissions</strong> :</li></ol><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="o">*</span><span class="w"/><span class="o">**</span><span class="n">Insecure</span><span class="o">**</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">Does</span><span class="w"/><span class="n">not</span><span class="w"/><span class="n">explicitly</span><span class="w"/><span class="n">set</span><span class="w"/><span class="n">file</span><span class="w"/><span class="n">permissions</span><span class="p">,</span><span class="w"/><span class="n">which</span><span class="w"/><span class="n">may</span><span class="w"/><span class="n">result</span><span class="w"/><span class="n">in</span><span class="w"/><span class="n">insecure</span><span class="w"/><span class="k">default</span><span class="w"/><span class="n">permissions</span><span class="p">.</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">*</span><span class="w"/><span class="o">**</span><span class="n">Secure</span><span class="o">**</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">Explicitly</span><span class="w"/><span class="n">sets</span><span class="w"/><span class="n">restrictive</span><span class="w"/><span class="n">file</span><span class="w"/><span class="nf">permissions</span><span class="w"/><span class="p">(</span><span class="n">rw</span><span class="o">-------</span><span class="p">),</span><span class="w"/><span class="n">ensuring</span><span class="w"/><span class="n">only</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">owner</span><span class="w"/><span class="n">can</span><span class="w"/><span class="n">read</span><span class="w"/><span class="n">and</span><span class="w"/><span class="n">write</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">file</span><span class="p">.</span></span></span></code></pre></div></div><ol start="3"><li><strong>Exception Handling</strong> :</li></ol><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="o">*</span><span class="w"/><span class="o">**</span><span class="n">Both</span><span class="w"/><span class="n">examples</span><span class="o">**</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">Handle</span><span class="w"/><span class="n">exceptions</span><span class="p">,</span><span class="w"/><span class="n">but</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">secure</span><span class="w"/><span class="n">example</span><span class="w"/><span class="n">properly</span><span class="w"/><span class="n">handles</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="n">that</span><span class="w"/><span class="n">may</span><span class="w"/><span class="n">arise</span><span class="w"/><span class="n">during</span><span class="w"/><span class="n">file</span><span class="w"/><span class="n">operations</span><span class="p">.</span></span></span></code></pre></div></div><p>By following the secure example, you can mitigate the risks associated with CWE-377 and ensure that temporary files are created securely in your Javalin applications.</p><h3 id="conclusion-on-cwe-377-insecure-temporary-file">Conclusion on CWE-377: Insecure Temporary File</h3><p>CWE-377, concerning the insecure creation and management of temporary files, is a critical security vulnerability that can have far-reaching consequences if not adequately addressed. In the context of Java, this weakness often arises due to predictable file names, inadequate file permissions, and race conditions such as Time-of-Check to Time-of-Use (TOCTOU). These issues can lead to severe threats, including unauthorised access, data tampering, information leakage, and even denial of service.</p><p>To mitigate the risks associated with CWE-377, developers must adopt secure practices when handling temporary files. This includes using secure and atomic file creation methods, like<code>**File.createTempFile()**</code> and<code>**Files.createTempFile()**</code>, which generate unique and unpredictable file names while ensuring the atomicity of operations. Proper file permissions must also be enforced to limit access to temporary files, preventing unauthorised users from reading or modifying them.</p><p>Understanding the nuances of race conditions like TOCTOU is essential in designing secure applications. By eliminating the gap between checking a file’s existence and subsequent use, developers can prevent attackers from exploiting the system during that critical window.</p><p>In summary, addressing CWE-377 requires a combination of secure coding practices, careful file permissions management, and robust APIs that inherently mitigate common pitfalls associated with temporary files. By adhering to these practices, Java developers can protect their applications from the potentially severe impacts of insecure temporary file management, ultimately ensuring their software systems&rsquo; confidentiality, integrity, and availability.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Security</category><media:content url="https://svenruppert.com/images/2024/08/9E2CC974-17A8-40DF-80BA-ED4E043F93DE.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/08/9E2CC974-17A8-40DF-80BA-ED4E043F93DE.jpg"/><enclosure url="https://svenruppert.com/images/2024/08/9E2CC974-17A8-40DF-80BA-ED4E043F93DE.jpg" type="image/jpeg" length="0"/></item><item><title>Vaadin Flow - How to start</title><link>https://svenruppert.com/posts/vaadin-flow-how-to-start/</link><pubDate>Wed, 19 Jun 2024 15:34:50 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/vaadin-flow-how-to-start/</guid><description> We will now create a new Vaadin Flow application step by step and create a basic framework for our own projects with this component-based open-source web framework. So, right from the start, the question arises: How can you start with as little effort as possible without avoiding the usual expenses that sometimes come with creating projects?</description><content:encoded>&lt;![CDATA[<figure><img src="/images/2024/06/image.png" alt="" loading="lazy" decoding="async"/><p>We will now create a new Vaadin Flow application step by step and create a basic framework for our own projects with this component-based open-source web framework. So, right from the start, the question arises: How can you start with as little effort as possible without avoiding the usual expenses that sometimes come with creating projects?</p><h2 id="how-and-where-do-i-start-my-project">How and where do I start my project?</h2><p>First we go to the URL start.vaadin.com. Here, we get to an application that allows us to generate a Vaadin project. Here, we choose the option “<strong>Hello World Project</strong> " out of. As soon as you get to the field “<strong>Edit</strong> ” on the right side with the mouse, the selection options shown here open. In this article, I will use Settings<strong>a</strong>) Flow / Java,<strong>b</strong>) Java,<strong>c</strong>) Maven,<strong>d</strong>) Servlet.</p><figure><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXeG0B92jdGQk8mJsaAnrPWO5L8RZWwlcTX58YdBaGvDiBd5PUuseDiYxaUoxMGGA2t6Q9vmXpgsrhWYy7TYZRGWBtfqb92-GV5Mwb2lbRpzvUjiuaIuB9QwuuARxnFmSFIQLOFIEEHLMZjVkOnS1ZGasN4w?key=MZRMMhZm3PwT2_D-9NmSGw" alt="" loading="lazy" decoding="async"/><p>I chose this setting specifically because I don&rsquo;t want to have any additional technological dependencies on other frameworks at this point, making the project as small as possible.</p><p>After downloading the packed project, you can unpack it in a folder of your choice and get the complete directory structure necessary for a Maven project. To work with the project, please open the project with the IDE of your choice. I will use the IDE IntelliJ (<a href="https://www.jetbrains.com/idea/">https://www.jetbrains.com/idea/</a>) from JetBrains in the community version in this article. This is freely available on all relevant platforms (Windows, Linux, and OSX). At this point I am assuming that a JDK and Maven are already installed and configured to work on the development computer.</p><p>After the project has been opened, you can do the first test to see whether everything is working. You can do this, for example, on the command line within the project directory with the following Maven command.<strong>mvn clean verify</strong> If everything runs through to the end without any error messages, you have downloaded all the necessary dependencies from the relevant repositories to your computer using Maven.</p><p>With the command<strong>mvn clean package jetty:run,</strong> you can also try out the project straight away. Everything is translated, assembled, and started within the servlet container Jetty. If you now access the URL with a browser,<strong>http://localhost:8080/,</strong> you will see the Vaadin Flow application.</p><figure><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXcBbjZ0WTRl-S5A7Hq2uqNL4Jzn5NtVbkHCzcYmDoQhkcet5r7LLTyGB-miF11Tczl9XQ4fINkMEiKFR_vQ8rSEzrL7I3z1uR4acg7LjtL5WdVRjKhBKCxvYt296yq1bYesHjKNUUAsMEnrx6oLEWtoUxc?key=MZRMMhZm3PwT2_D-9NmSGw" alt="" loading="lazy" decoding="async"/><p>Now, you can try out how this application works. by entering something into the input field and then clicking the button labelled “<strong>Say hello</strong> ” clicks. In my case, I entered my name, “Sven Ruppert”, and activated the button once. The result then looks like this in my browser.</p><figure><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXeqImDjCHnZFgD5qTfO51us3NDDmUmzv58orJe9oeYlDk9ur_tFduG9c6klISjgoM4oSdDwx0vZShs1mKwS1BXjKoU_Kq7LTBsjqGavw4y9QFyPR1LwQLFlZyRnpBaHmtxOibgO2SiRhBA5aJd0lpHMyEOj?key=MZRMMhZm3PwT2_D-9NmSGw" alt="" loading="lazy" decoding="async"/><h2 id="what-is-included-in-the-project">What is included in the project?</h2><p>But how can you program this yourself? To do this, we first end the ongoing process that we are using<strong>mvn</strong> have started.</p><p>First, we edit the pom.xml a little. Here, we will limit Vaadin&rsquo;s dependency on pure open-source components. This saves time and space during development. For this purpose, the definition “<strong>vaadin</strong> ” changed in &ldquo;<strong>vaadin-core</strong> ”</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;groupId&gt;</span>com.vaadin<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">   <span class="nt">&lt;artifactId&gt;</span>vaadin-core<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><h2 id="the-application-logic">The application logic</h2><p>The example application consists of three Java classes. We&rsquo;ll now start with the business logic we used in the class GreetingService find. There&rsquo;s not much to say about this, as it&rsquo;s a good old plain Java class. The goal is to transform the input string passed so that the greeting comes out. We don&rsquo;t find any Vaadin elements here yet.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">GreetService</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">greet</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">name</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">name</span><span class="p">.</span><span class="na">isEmpty</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">return</span><span class="w"/><span class="s">"Hello anonymous user"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="k">return</span><span class="w"/><span class="s">"Hello "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Then, we are missing two more Java classes, which we can find in the source code folder. On the one hand, it’s the class ”MainView&rdquo;. The entire visual design of this demo app is implemented here.</p><h2 id="how-do-i-define-the-gui">How do I define the GUI?</h2><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="s">""</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="nf">MainView</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">TextField</span><span class="w"/><span class="n">textField</span><span class="w"> </span></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="p">(</span><span class="s">"Your name"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">GreetService</span><span class="w"/><span class="n">greetService</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GreetService</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">Button</span><span class="w"/><span class="n">button</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="p">(</span><span class="s">"Say hello"</span><span class="p">,</span><span class="w"/><span class="n">and</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="n">String</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">textField</span><span class="p">.</span><span class="na">getValue</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="n">String</span><span class="w"/><span class="n">greet</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">greetService</span><span class="p">.</span><span class="na">greet</span><span class="p">(</span><span class="n">value</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="n">Paragraph</span><span class="w"/><span class="n">paragraph</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Paragraph</span><span class="p">(</span><span class="n">greet</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="n">add</span><span class="p">(</span><span class="n">paragraph</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">           </span><span class="p">});</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">add</span><span class="p">(</span><span class="n">textField</span><span class="p">,</span><span class="w"/><span class="n">button</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Let&rsquo;s go through this together. First, we see the class definition “<strong>MainView</strong> ”, which arises from deriving the class “<strong>VerticalLayout</strong> ” results. This is a layout component in which the elements are arranged vertically inside. Within the constructor, we now define the various visible elements. In this case, it is an input field and a button. At the very end, these are in the order of text field and button with the method<strong>add(..)</strong> added to the VerticalLayout. The<strong>GreetingService</strong> is created within the constructor, and then within the<strong>ActionListeners,</strong> the button is used. For this example, the procedure shown is exemplary. However, we will examine when you should use other design patterns in a later article. The button here has a clearly defined English label, “<strong>Say hello</strong> ”, that the user sees. Things get more interesting when defining behaviour. When you click the button, the values ​​are read from the text field using the instance of<strong>GreetingServers</strong> modified. This new value is then embedded in a graphical component of the Paragraph type and passed to the external layout as a child node.</p><p>Here, you can see quite clearly that you don&rsquo;t have to worry too much about the typical web application challenges during development. You can use everything that the Java language offers in terms of abstractions.</p><p>The MainView class has an annotation called “<strong>@Route(“”)</strong> ”. Die Annotation “<strong>@Route</strong> ” is used in Vaadin to map a view component (e.g. a UI) to a specific URL. It indicates which class is the web application&rsquo;s main view for a particular path. This allows the user interface to be accessed via a specific URL in the browser.</p><p>The annotation&rsquo;s value specifies the path under which the view can be reached. If no path is specified, the view is accessible at the root path ("/").</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Route</span><span class="p">(</span><span class="s">""</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="c1">// View content</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the<code>**MainView**</code> is under the root path (“<strong>http://localhost:8080/</strong> &ldquo;) reachable. You can also specify a specific path to make the view accessible at a specific URL.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Route</span><span class="p">(</span><span class="s">"all"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ToDoView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="c1">// View content</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span></span></span></code></pre></div></div><p>The “<strong>ToDoView</strong> " under the path &ldquo;<strong>http://localhost:8080/todo</strong> " is reachable in this example.</p><p>The annotation allows Vaadin to control navigation within the application. Users can navigate to different views by changing the URL in the browser or using programmatic navigation methods. The &ldquo;<strong>@Route</strong> ” annotation can also be used with a layout. A layout is a high-level component that contains multiple views. Here is an example:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Route</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"todo"</span><span class="p">,</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainLayout</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ToDoView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="c1">// View content</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span></span></span></code></pre></div></div><p>In this example, “<strong>ToDoView</strong> ” within the &ldquo;<strong>MainLayout</strong> ” is displayed if the URL “<strong>http://localhost:8080/todo</strong> ” is called. You can also use dynamic parameters in the URL to display specific content in the view.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="nd">@Route</span><span class="p">(</span><span class="s">"user/:userID"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">UserView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="c1">// View content</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span></span></span></code></pre></div></div><p>In this example, “<strong>UserView</strong> ” at a URL like “<strong>http://localhost:8080/user/123</strong> ” is called, whereas “<strong>123</strong> ” is a dynamic parameter.</p><p>In summary, the<code>**@Route**</code> annotation defines URL mapping to View classes in Vaadin, which enables navigation and access to various parts of the web application.</p><h2 id="how-can-i-configure-the-web-app">How can I configure the web app?</h2><p>Now, we&rsquo;re missing the class<strong>AppShell</strong> , which is the implementation of the interface<strong>AppSehllConfigurator</strong>.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@PWA</span><span class="p">(</span><span class="n">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Project Base for Vaadin"</span><span class="p">,</span><span class="w"/><span class="n">shortName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Project Base"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Push</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Theme</span><span class="p">(</span><span class="s">"my-theme"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">AppShell</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">AppShellConfigurator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The interface ”<strong>AppShellConfigurator</strong> ” in Vaadin Flow defines the general configuration of the HTML shell of your Vaadin application. This mainly concerns the “<strong>&lt; head&gt;</strong>” area of ​​the HTML document where meta tags, titles, links to external resources (such as CSS files or favicons), and similar configurations can be set.</p><p>Here, for example, I added “Dark” to the theme definition, which now gives the following look.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@PWA</span><span class="p">(</span><span class="n">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Project Base for Vaadin"</span><span class="p">,</span><span class="w"/><span class="n">shortName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Project Base"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Push</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="nd">@Theme</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"my-theme"</span><span class="p">,</span><span class="w"/><span class="n">variant</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Lumo</span><span class="p">.</span><span class="na">DARK</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">AppShell</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">AppShellConfigurator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><figure><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXcM81QEk6K1ySkY_mLqeAGzxLXrxSJ4c50uM4sIwrhGSFAyXUSHD_BVUqc-om7GCVR1M5ki8dQYHKHGiyCuyCsUQ-qWMbj3_6k0UqcPxk4TlJvEmi7Z96mJrVNR6E7j-nZmpJhyt7lTSmBQX2YQVrd9Hsny?key=MZRMMhZm3PwT2_D-9NmSGw" alt="" loading="lazy" decoding="async"/><p>Let’s go back to the method”<strong>configurePage</strong> ” in Vaadin Flow. Here, you can configure the general HTML shell of your application. This method customises various aspects of the HTML document, especially the “<strong>&lt; head&gt;</strong>"-Area. Here are the main adjustments you can make:</p><p>You can set the HTML page title. The page title is displayed in the browser tab and is essential for user experience and search engine optimisation.</p><p>Meta tags are essential for providing metadata about HTML documents. With &ldquo;<strong>configurePage</strong> ” you can add different meta tags, such as:</p><ul><li><strong>Viewport meta tag</strong> : This is especially important for responsive designs as it determines how the page will appear on different devices and screen sizes.</li><li><strong>Description</strong> : A meta tag to describe the content of your page, which is vital for search engine optimisation (SEO).</li><li><strong>Keywords</strong> : Keywords that describe the page&rsquo;s content and can improve SEO.</li></ul><p>Favicons are tiny icons that appear in browser tabs and bookmarks. You can set different sizes and types of favicons to ensure they look good on all devices and platforms. You can add links to external resources such as CSS stylesheets or JavaScript files. This is useful for including additional libraries or styles in your application.</p><p>For Progressive Web Apps (PWAs), you can use the “<strong>configurePage</strong> ” method to configure the manifest and service worker. The manifest defines how the application displays on the home screen of a mobile device while using the service worker for offline functionality and push notifications.</p><p>To increase the security of your application, you can add various security-related meta tags, such as Content Security Policy (CSP).</p><p>Social media tags such as Open Graph (for Facebook) or Twitter Cards can be added to determine how your pages appear when shared on social networks. For mobile applications, you can set specific settings that improve the appearance and behaviour of your web application on mobile devices, such as the status bar colour and the app icon on the home screen.</p><p>By using the method “<strong>configurePage</strong> ”, you can ensure that all critical meta information and resources are consistently and correctly integrated into every HTML page in your application. This contributes to a better user experience, optimised performance and improved web application security.</p><h2 id="which-are-there-any-components">Which Are there any components?</h2><p>After discussing the existing classes, we can modify the GUI a bit. We will now try out various components a little. The first question is, what categories of components are there in Vaadin Flow?</p><p>There are a variety of components in Vaadin Flow that can be grouped into different categories. Here are the main types of components with some examples:</p><h3 id="basic-layout-components">Basic layout components:</h3><ul><li><strong>VerticalLayout</strong> : Arranges components vertically.</li><li><strong>HorizontalLayout</strong> : Arranges components horizontally.</li><li><strong>Div</strong> : A simple container that works like a<code>&lt;div&gt;</code> element in HTML.</li><li><strong>FormLayout</strong> : Explicitly designed for form layouts to arrange input fields in a grid.</li></ul><h3 id="input-components">Input components:</h3><ul><li>TextField: Simple text input.</li><li>TextArea: Multi-line text input.</li><li>PasswordField: Input field for passwords in which the characters are hidden.</li><li>ComboBox: A drop-down menu with choices.</li><li>DatePicker: Selecting a date.</li><li>CheckBox: A checkbox.</li><li>RadioButtonGroup: A group of radio buttons.</li><li>Select: A drop-down selection component.</li></ul><h3 id="button-components">Button components:</h3><ul><li><strong>Button</strong> : A simple button that can be clicked.</li><li><strong>NativeButton</strong> : A button that behaves like a native HTML button.</li><li><strong>Checkbox</strong> : A checkbox.</li></ul><h3 id="display-and-information-components">Display and information components:</h3><ul><li><strong>Label</strong> : A simple text label.</li><li><strong>Html</strong> : Component for displaying HTML content.</li><li><strong>Image</strong> : To view images.</li><li><strong>Notification</strong> : To view notifications.</li><li><strong>ProgressBar</strong> : A progress bar.</li><li><strong>Badge</strong> : To display small blocks of information or counters.</li></ul><h3 id="navigation-components">Navigation components:</h3><ul><li><strong>MenuBar</strong> : A menu bar.</li><li><strong>Tabs</strong> : Tabs for navigation between different views.</li><li><strong>Accordion</strong> : An accordion component for collapsible content.</li><li><strong>DrawerToggle</strong> : A toggle for a side menu.</li></ul><h3 id="grid-and-data-displays">Grid and data displays:</h3><ul><li><strong>Grid</strong> : A robust table for displaying data.</li><li><strong>TreeGrid</strong> : A tree-structured grid for displaying hierarchical data.</li><li><strong>ListBox</strong> : A simple list to display choices.</li><li><strong>DataView</strong> : To display data in a structured form.</li></ul><h3 id="dialogues-and-overlays">Dialogues and overlays:</h3><ul><li><strong>Dialog</strong> : A modal dialogue for user interactions.</li><li><strong>Overlay</strong> : To overlay content on the current view.</li></ul><h3 id="multimedia-components">Multimedia components:</h3><ul><li><strong>Audio</strong> : For embedding and playing audio files.</li><li><strong>Video</strong> : For embedding and playing video files.</li></ul><h3 id="forms-and-validation">Forms and validation:</h3><ul><li><strong>FormLayout</strong> : Used to arrange form fields in a layout.</li><li><strong>Binder</strong> : For binding forms to data models and validation.</li></ul><h3 id="special-and-advanced-components">Special and advanced components:</h3><ul><li><strong>Upload</strong> : To upload files.</li><li><strong>RichTextEditor</strong> : A WYSIWYG editor for rich text input.</li><li><strong>SplitLayout</strong> : To divide the layout into two areas using a sliding divider.</li><li><strong>DateTimePicker</strong> : To select the date and time.</li></ul><p>These components cover many use cases and enable developers to create rich and interactive web applications. Vaadin Flow also offers the ability to create and extend custom components to meet specific needs.</p><h2 id="hello-world-for-advanced-users">Hello World for advanced users</h2><p>Let&rsquo;s now move on to the practical application of individual components. We are writing a small application that includes multiple input fields, a table to display data, and easy navigation between two views. This extension showcases more of Vaadin&rsquo;s capabilities, including layouts, forms, data bindings, and navigation. However, we have not yet gone into the concepts of industrial applications. We will address this in detail in the next post.</p><p>If you have different views, the question almost automatically arises as to how you can define the general appearance of the application. Layouts in which the primary division of the application is defined are suitable for this. Vaadin Flow offers you the component<strong>AppLayout</strong> at this point. This is a simple layout structure that already provides you with elements such as a navigation bar and a workspace. This is precisely what we will use now. For this, we create the class<strong>MainLayout</strong> and direct them from<strong>AppLayout</strong> away. In the constructor, we define the desired structure. In our example, we would like to receive two entries for navigation. Firstly, access to the existing MainView and secondly to a new view with the name<strong>DataView</strong> to record and display a personal data record. The class<strong>RouterLink</strong> is the required element to be added to a navigation bar.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainLayout</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">AppLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">public</span><span class="w"/><span class="nf">MainLayout</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">createHeader</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">createHeader</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">RouterLink</span><span class="w"/><span class="n">mainLink</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RouterLink</span><span class="p">(</span><span class="s">"Hello World"</span><span class="p">,</span><span class="w"/><span class="n">MainView</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">RouterLink</span><span class="w"/><span class="n">dataLink</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RouterLink</span><span class="p">(</span><span class="s">"Data View"</span><span class="p">,</span><span class="w"/><span class="n">DataView</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="n">addToNavbar</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="p">(</span><span class="n">mainLink</span><span class="p">,</span><span class="w"/><span class="n">dataLink</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"> </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>So the class<strong>MainView</strong> Now knowing that it is within the layout is in the annotation<strong>@Route</strong> the class<strong>MainLayout</strong> specified in the layout attribute.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@Route</span><span class="p">(</span><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">""</span><span class="p">,</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MainLayout</span><span class="p">.</span><span class="na">class</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainView</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">VerticalLayout</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//COLLAR</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This way, a nested layout can be quickly built with Vaadin Flow.</p><p>Now, let&rsquo;s move on to the second view,<strong>DataView</strong>. Here, we can do the same thing as<strong>MainView</strong> proceeds.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">@Route(value = "data", layout = MainLayout.class)</span></span><span class="line"><span class="cl">public class DataView extends VerticalLayout {</span></span><span class="line"><span class="cl"> private List<span class="nt">&lt;Person&gt;</span> people = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> private Grid<span class="nt">&lt;Person&gt;</span> grid = new Grid<span class="err">&lt;</span>&gt;(Person.class);</span></span><span class="line"><span class="cl"> private ListDataProvider<span class="nt">&lt;Person&gt;</span> dataProvider</span></span><span class="line"><span class="cl">     = new ListDataProvider<span class="err">&lt;</span>&gt;(people);</span></span><span class="line"><span class="cl"> public DataView() {</span></span><span class="line"><span class="cl">   was firstNameField = new TextField("First Name");</span></span><span class="line"><span class="cl">   was lastNameField = new TextField("Last Name");</span></span><span class="line"><span class="cl">   was addButton = new Button("Add Person",</span></span><span class="line"><span class="cl">       event -&gt; {</span></span><span class="line"><span class="cl">         people.add(</span></span><span class="line"><span class="cl">             new Person(firstNameField.getValue(),</span></span><span class="line"><span class="cl">                        lastNameField.getValue()));</span></span><span class="line"><span class="cl">         dataProvider.refreshAll();</span></span><span class="line"><span class="cl">       });</span></span><span class="line"><span class="cl">   grid.setDataProvider(dataProvider);</span></span><span class="line"><span class="cl">   grid.setColumns("firstName", "lastName");</span></span><span class="line"><span class="cl">   add(firstNameField, lastNameField, addButton, grid);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>The<strong>DataView</strong> is also derived from one<strong>VerticalLayout</strong>. First, we define the required infrastructure elements. In this case, there is a list for the people, a table to display them, and a<strong>ListDataProvider</strong> , which is the connection between the graphical representation and the data itself. I won&rsquo;t go into the specific properties here, just this much in advance: the attributes of the class<strong>Person</strong> are the headings of the columns in the table.</p><p>Now, we define the appearance and dynamic behaviour of the displayed components. This time, there are two text fields for entering the data and a button for adding the data to the table. The<strong>ActionListener</strong> of the button creates an instance of the class from the values ​​of the two input fields<strong>Person.</strong> This instance is added to the list and subsequently informed to the<strong>DataProvider</strong> that there has been a change to the data. So that the<strong>DataProvider</strong> and the table know about each other, they are made known to each other. This is done by passing the<strong>DataProvider</strong> to the table.</p><p>Finally, all visible elements are added to the<strong>DataView</strong>.</p><p>If you start the application now, you will get the following views:</p><p><figure><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXfMeUh6wkOZ3CYk2WhYar9aWsnuL_G4YocPSlmiNHJXuxsDnS8JXUC4GoXpBzPXl3vPpbCrxpcclrSO_DhB_FUW1VhhUMWtHYPldZ91_EMpHB99eMErj1YCEa103B22TYindUTtIdzbq0BuOLNrJZG-TCI?key=MZRMMhZm3PwT2_D-9NmSGw" alt="" loading="lazy" decoding="async"/><figure><img src="https://lh7-us.googleusercontent.com/docsz/AD_4nXfjBGgQeyKxA7i0p9fy3Z7aZUVHJYEBhvsPWDYUgocoTj0VoloRRLnlfXZ_2wMHBI_WAKXtMf6qFLONvN6I0GIJN35nFoBmvPEnR-HYoHK-3uL5ZGP2qKDWc0rQ57FGLjyOI9zjZ2uUi-eKTEnVEG_Cuws?key=MZRMMhZm3PwT2_D-9NmSGw" alt="" loading="lazy" decoding="async"/></p><p>We now have a rudimentary basis for our projects with Vaadin Flow. The following article will look at how we can expand the project.</p><p>I have placed the example on Github using the following URL.</p><p>Until then, have fun with your experiments.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Java</category><category>Vaadin</category><media:content url="https://svenruppert.com/images/2024/06/DALL%C2%B7E-2024-06-19-15.05.26-A-visualization-of-a-Hello-World-project-using-Vaadin-Flow-in-a-forest-theme-without-using-any-words-characters-or-letters.-Show-a-modern-web-appli.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/06/DALL%C2%B7E-2024-06-19-15.05.26-A-visualization-of-a-Hello-World-project-using-Vaadin-Flow-in-a-forest-theme-without-using-any-words-characters-or-letters.-Show-a-modern-web-appli.jpeg"/><enclosure url="https://svenruppert.com/images/2024/06/DALL%C2%B7E-2024-06-19-15.05.26-A-visualization-of-a-Hello-World-project-using-Vaadin-Flow-in-a-forest-theme-without-using-any-words-characters-or-letters.-Show-a-modern-web-appli.jpeg" type="image/jpeg" length="0"/></item><item><title>Comparing Code Coverage Techniques: Line, Property-Based, and Mutation Testing</title><link>https://svenruppert.com/posts/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/</link><pubDate>Fri, 31 May 2024 09:20:30 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/</guid><description>What is Test Coverage? Test coverage is a metric used in software testing to measure the testing performed on a piece of software. It indicates how thoroughly a software program has been tested by identifying which parts of the code have been executed (covered) during testing and which have not. Here are the key aspects of test coverage:</description><content:encoded>&lt;![CDATA[<h2 id="what-is-test-coverage">What is Test Coverage?</h2><p>Test coverage is a metric used in software testing to measure the testing performed on a piece of software. It indicates how thoroughly a software program has been tested by identifying which parts of the code have been executed (covered) during testing and which have not. Here are the key aspects of test coverage:</p><ol><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#what-is-test-coverage">What is Test Coverage?</a><ol><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#purpose">Purpose:</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#examples-of-types-of-test-coverage">Examples of Types of Test Coverage:</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#benefits">Benefits:</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#limitations">Limitations:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#what-is-the-history-of-test-coverage">What is the history of Test Coverage?</a><ol><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#early-days-of-software-testing-1950s-1960s">Early Days of Software Testing (1950s-1960s)</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#introduction-of-code-coverage-1970s">Introduction of Code Coverage (1970s)</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#development-of-coverage-tools-1980s-1990s">Development of Coverage Tools (1980s-1990s)</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#rise-of-agile-and-test-driven-development-2000s">Rise of Agile and Test-Driven Development (2000s)</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#modern-coverage-tools-and-practices-2010s-present">Modern Coverage Tools and Practices (2010s-Present)</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#key-figures-and-influential-works">Key Figures and Influential Works</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#trends-and-future-directions">Trends and Future Directions</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#the-basic-line-coverage">The Basic: Line Coverage</a><ol><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#key-concepts-of-line-coverage">Key Concepts of Line Coverage</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#how-line-coverage-works">How Line Coverage Works</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#example-workflow-with-jacoco">Example Workflow with JaCoCo</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#interpreting-line-coverage-reports">Interpreting Line Coverage Reports</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#best-practices">Best Practices</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#limitations-1">Limitations</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#property-based-testin-coverage">Property Based Testin Coverage</a><ol><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#key-concepts-of-property-based-testing">Key Concepts of Property-Based Testing</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#popular-libraries-for-property-based-testing-in-java">Popular Libraries for Property-Based Testing in Java</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#how-to-use-property-based-testing-in-java">How to Use Property-Based Testing in Java</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#benefits-of-property-based-testing">Benefits of Property-Based Testing</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#challenges-and-best-practices">Challenges and Best Practices</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#mutation-based-testing-coverage">Mutation-Based Testing Coverage</a><ol><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#key-concepts-of-mutation-testing">Key Concepts of Mutation Testing</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#steps-in-mutation-testing">Steps in Mutation Testing</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#pitest-for-mutation-testing-in-java">Pitest for Mutation Testing in Java</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#example-of-mutation-testing">Example of Mutation Testing</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#benefits-of-mutation-testing">Benefits of Mutation Testing</a></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#challenges-and-best-practices">Challenges and Best Practices</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/31/comparing-code-coverage-techniques-line-property-based-and-mutation-testing/#conclusion">Conclusion</a></li></ol><h3 id="purpose-"><strong>Purpose</strong> :</h3><p>The primary purpose of test coverage is to ensure that the code has been exercised to the maximum extent possible, reducing the chances of undiscovered bugs. It helps identify untested parts of the code, enabling testers to create additional tests to cover those areas.</p><h3 id="examples-of-types-of-test-coverage-"><strong>Examples of Types of Test Coverage</strong> :</h3><p><strong>Code Coverage</strong> : Measures the extent to which the source code is tested. It includes various levels like:</p><p><strong>Statement Coverage</strong> : Ensures that each statement in the code has been executed at least once.</p><p><strong>Branch Coverage</strong> : Ensures that each branch (true/false) of control structures (like if statements) has been executed.</p><p><strong>Function Coverage</strong> : Ensures that each function in the code has been called and executed.</p><p><strong>Condition Coverage</strong> : Ensures that each boolean expression has been evaluated to be both true and false.</p><p><strong>Feature Coverage</strong> : Measures how many features or requirements have been tested.</p><p><strong>Path Coverage</strong> : Ensures that all possible paths in the code have been executed.</p><h3 id="benefits">Benefits:</h3><p><strong>Improved Quality</strong> : Higher test coverage generally leads to higher quality software as more defects are likely to be found and fixed.</p><p><strong>Risk Management</strong> : Identifying untested parts of the code helps manage risks by thoroughly testing critical areas.</p><p><strong>Maintenance</strong> : Helps maintain the code by testing new changes and not introducing new bugs.</p><h3 id="limitations">Limitations:</h3><p><strong>False Sense of Security</strong> : High coverage does not guarantee the software is bug-free. It only indicates that the tests have executed the code.</p><p><strong>Focus on Quantity</strong> : Focusing too much on coverage percentages can lead to writing tests that simply increase coverage without effectively testing the functionality.</p><p><strong>Complexity</strong> : Achieving 100% coverage can be difficult and time-consuming, especially for large and complex codebases.</p><p>Overall, test coverage is a valuable metric in the software development lifecycle. It provides insights into the effectiveness of the testing process and highlights areas that may need additional attention.</p><h2 id="what-is-the-history-of-test-coverage">What is the history of Test Coverage?</h2><p>The history of test coverage can be traced back to the evolution of software engineering and the increasing complexity of software systems, which necessitated more rigorous and systematic testing methodologies. Here are some key milestones and developments in the history of test coverage:</p><h3 id="early-days-of-software-testing-1950s-1960s">Early Days of Software Testing (1950s-1960s)</h3><p><strong>Initial Testing Practices</strong> : In the early days of computing, software testing was often ad hoc, with no formal methods or metrics. Testing was primarily focused on ensuring that the software ran without crashing.</p><p><strong>Emergence of Formal Methods</strong> : The need for systematic testing approaches became evident as software systems became more complex. Early researchers and practitioners started formalising testing techniques.</p><h3 id="introduction-of-code-coverage-1970s">Introduction of Code Coverage (1970s)</h3><p><strong>Code Coverage Concept</strong> : Code coverage emerged in the 1970s to measure how thoroughly software tests exercised the code. This period saw the development of various coverage criteria, such as statement coverage, branch coverage, and path coverage.</p><p><strong>Myers&rsquo; Work</strong> : Glenford Myers&rsquo; book The Art of Software Testing, first published in 1979, was influential in promoting structured and systematic testing practices, including the use of coverage metrics.</p><h3 id="development-of-coverage-tools-1980s-1990s">Development of Coverage Tools (1980s-1990s)</h3><p><strong>Early Tools</strong> : In the 1980s and 1990s, early test coverage tools were developed that automated code coverage measurement. These tools helped developers and testers visualise and quantify the extent of their testing efforts.</p><p><strong>Integration with Development Environments</strong> : As integrated development environments (IDEs) and automated testing frameworks became more common, coverage tools began to be integrated into these environments, making it easier for developers to incorporate coverage measurement into their workflows.</p><h3 id="rise-of-agile-and-test-driven-development-2000s">Rise of Agile and Test-Driven Development (2000s)</h3><p><strong>Agile Methodologies</strong> : The adoption of Agile methodologies in the early 2000s emphasised iterative development and continuous testing, leading to a greater focus on test coverage to ensure code quality.</p><p><strong>Test-Driven Development (TDD)</strong> : TDD, a practice where tests are written before the code itself, gained popularity. This approach inherently promoted high levels of test coverage, as each piece of code was written to pass specific tests.</p><h3 id="modern-coverage-tools-and-practices-2010s-present">Modern Coverage Tools and Practices (2010s-Present)</h3><p><strong>Advanced Coverage Tools</strong> : Modern coverage tools have become more sophisticated, offering features such as real-time coverage analysis, integration with CI/CD pipelines, and detailed reporting. Examples include JaCoCo for Java, Istanbul for JavaScript, and Coverage.py for Python.</p><p><strong>Shift-Left Testing</strong> : The shift-left testing approach, which advocates for testing early and often in the development lifecycle, has further reinforced the importance of test coverage. Tools and practices have evolved to support this approach, ensuring that coverage metrics are available throughout development.</p><p><strong>Code Quality Platforms</strong> : Comprehensive code quality platforms like SonarQube have integrated test coverage metrics with other quality indicators, providing a holistic view of software health.</p><h3 id="key-figures-and-influential-works">Key Figures and Influential Works</h3><p><strong>Glenford Myers</strong> : His seminal work &ldquo;The Art of Software Testing&rdquo; laid the foundation for systematic software testing and introduced many critical concepts related to test coverage.</p><p><strong>Tom DeMarco and Tim Lister</strong> : Their work on software metrics and quality assurance highlighted the importance of measuring and improving software processes, including testing practices.</p><h2 id="trends-and-future-directions">Trends and Future Directions</h2><p><strong>Increased Automation</strong> : As software development continues towards greater automation, test coverage tools are becoming more integrated with automated testing frameworks and CI/CD pipelines.</p><p><strong>AI and Machine Learning</strong> : Emerging technologies like AI and machine learning are being applied to testing and coverage analysis, helping to identify untested areas more intelligently and predict potential risks based on coverage data.</p><p><strong>Focus on Test Effectiveness</strong> : While achieving high test coverage is important, there is a growing emphasis on test effectiveness. This includes ensuring that tests not only cover code but also validate that the code behaves correctly under various conditions.</p><p>Test coverage has evolved significantly over the decades from a conceptual metric to a critical component of modern software development practices, helping to ensure the quality and reliability of software systems.</p><h2 id="the-basic-line-coverage">The Basic: Line Coverage</h2><p>Line coverage is a code coverage metric that measures whether each line of source code in a program has been executed during testing. In Java, line coverage helps developers understand how thoroughly their tests exercise their code by indicating which lines have been tested and which have not. Here’s an in-depth look at line coverage in Java:</p><h3 id="key-concepts-of-line-coverage">Key Concepts of Line Coverage</h3><p><strong>Definition</strong> : Statement coverage measures whether each line of code has been executed at least once during testing.</p><p><strong>Importance</strong> :</p><ul><li><strong>Bug Detection</strong> : Helps identify untested parts of the code that might contain bugs.</li><li><strong>Code Quality</strong> : Ensures that all parts of the code are tested, leading to better overall code quality.</li><li><strong>Maintenance</strong> : Aids in maintaining the code by ensuring that changes and new additions are tested.</li></ul><p><strong>Tools for Measuring Line Coverage in Java</strong> :</p><ul><li><strong>JaCoCo (Java Code Coverage)</strong> : A widely used open-source library for measuring code coverage in Java projects.</li><li><strong>Emma</strong> : Another popular tool but has mainly been superseded by JaCoCo.</li><li><strong>Cobertura</strong> : An older code coverage tool that is still used in some projects.</li><li><strong>EclEmma</strong> : An Eclipse plugin for JaCoCo, making visualising coverage directly in the IDE easy.</li></ul><h3 id="how-line-coverage-works">How Line Coverage Works</h3><p><strong>Instrumentation</strong> : Tools like JaCoCo instrument the bytecode of a Java program to insert probes at various points. These probes collect data on which lines of code are executed during the test run.</p><p><strong>Running Tests</strong> : Once the code is instrumented, the tests are executed. As the tests run, the probes collect execution data for each line of code.</p><p><strong>Reporting</strong> : After the tests are complete, the coverage tool generates a report showing which lines of code were executed. This report often includes visual indicators (such as colour coding) to show covered (executed) and uncovered (not executed) lines.</p><h3 id="example-workflow-with-jacoco">Example Workflow with JaCoCo</h3><p><strong>Adding JaCoCo to the Project</strong> :</p><p>For a Maven project, you add the JaCoCo plugin to the<code>pom.xml</code> file:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;build&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;plugins&gt;</span></span></span><span class="line"><span class="cl">         <span class="nt">&lt;plugin&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;groupId&gt;</span>org.jacoco<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;artifactId&gt;</span>jacoco-maven-plugin<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;version&gt;</span>0.8.8<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;executions&gt;</span></span></span><span class="line"><span class="cl">             <span class="nt">&lt;execution&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;goals&gt;</span></span></span><span class="line"><span class="cl">                 <span class="nt">&lt;goal&gt;</span>prepare-agent<span class="nt">&lt;/goal&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;/goals&gt;</span></span></span><span class="line"><span class="cl">             <span class="nt">&lt;/execution&gt;</span></span></span><span class="line"><span class="cl">             <span class="nt">&lt;execution&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;id&gt;</span>report<span class="nt">&lt;/id&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;phase&gt;</span>test<span class="nt">&lt;/phase&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;goals&gt;</span></span></span><span class="line"><span class="cl">                 <span class="nt">&lt;goal&gt;</span>report<span class="nt">&lt;/goal&gt;</span></span></span><span class="line"><span class="cl">               <span class="nt">&lt;/goals&gt;</span></span></span><span class="line"><span class="cl">             <span class="nt">&lt;/execution&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;/executions&gt;</span></span></span><span class="line"><span class="cl">         <span class="nt">&lt;/plugin&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;/plugins&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;/build&gt;</span></span></span></code></pre></div></div><p><strong>Running Tests</strong> :</p><p>Execute your tests using Maven:<strong>mvn clean test</strong></p><p><strong>Generating Coverage Report</strong> :</p><p>After running the tests, generate the coverage report:<strong>mvn jacoco:report</strong></p><p><strong>Viewing the Report</strong> :</p><p>The report is usually generated in the<code>**target/site/jacoco**</code> directory. To view the detailed coverage report, open the<code>index.html</code> file in a web browser.</p><h3 id="interpreting-line-coverage-reports">Interpreting Line Coverage Reports</h3><p><strong>Coverage Percentage</strong> : Indicates the proportion of lines executed versus the total number of lines.</p><p><strong>Highlighted Lines</strong> : Typically, green lines indicate covered lines, while red lines indicate uncovered lines.</p><p><strong>Detailed Metrics</strong> : Reports might include additional metrics like the number of covered and missed lines, branches, and methods.</p><h3 id="best-practices">Best Practices</h3><p><strong>Aim for High Coverage</strong> : While 100% coverage is ideal, strive for the highest coverage practical for your project to ensure maximum reliability.</p><p><strong>Focus on Critical Code</strong> : Ensure that the most critical and complex parts of the code are covered extensively.</p><p><strong>Regular Monitoring</strong> : Run coverage reports regularly as part of your CI/CD pipeline to catch regressions and ensure new code is adequately tested.</p><p><strong>Complement with Other Testing Techniques</strong> : Line coverage is one aspect of testing. For comprehensive coverage, use it alongside other techniques like unit, integration, and manual testing.</p><h3 id="limitations-1">Limitations</h3><p><strong>False Sense of Security</strong> : High-line coverage does not guarantee the absence of bugs. It’s possible to have tests covering all lines but not effectively testing the logic.</p><p><strong>Focus on Quantity over Quality</strong> : Simply aiming for high coverage numbers can lead to writing superficial tests that do not profoundly validate the code&rsquo;s behaviour.</p><p>Line coverage is a valuable metric for assessing the thoroughness of your tests in Java projects. Using tools like JaCoCo and following best practices ensures that your code is well-tested and maintains high-quality standards. However, it should be used as part of a broader testing strategy to achieve the best results.</p><h2 id="property-based-testin-coverage">Property Based Testin Coverage</h2><p>Property-based testing (PBT) is a testing methodology where instead of writing individual test cases with specific inputs and expected outputs, you define properties that should hold true for a wide range of inputs. The testing framework then automatically generates test cases to verify these properties. This can uncover edge cases and unexpected behaviours that might not be evident with traditional example-based testing.</p><h3 id="key-concepts-of-property-based-testing">Key Concepts of Property-Based Testing</h3><p><strong>Properties</strong> : These are general assertions about the behaviour of your code. Properties describe the expected behaviour for various inputs rather than specific examples.</p><p><strong>Generators</strong> : These automatically create input data for tests, allowing the exploration of a wide range of possible values.</p><p><strong>Shrinkers</strong> : When a test fails, shrinkers reduce the size of the failing input to the simplest form that still causes the failure, making it easier to diagnose the problem.</p><h3 id="popular-libraries-for-property-based-testing-in-java">Popular Libraries for Property-Based Testing in Java</h3><p><strong>jqwik</strong> : A modern property-based testing library for Java that integrates well with JUnit 5.</p><p><strong>QuickTheories</strong> : A property-based testing tool inspired by QuickCheck from Haskell, focusing on creating and shrinking data for tests.</p><p><strong>JUnit-QuickCheck</strong> : An extension for JUnit that brings property-based testing capabilities inspired by Haskell&rsquo;s QuickCheck to Java.</p><h3 id="how-to-use-property-based-testing-in-java">How to Use Property-Based Testing in Java</h3><p><strong>Adding jqwik to Your Project</strong> :</p><p>For a Maven project, add the following dependency to your<code>pom.xml</code>:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;groupId&gt;</span>net.jqwik<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;artifactId&gt;</span>jqwik<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;version&gt;</span>1.5.1<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;scope&gt;</span>test<span class="nt">&lt;/scope&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><p><strong>Writing a Property-Based Test</strong> :</p><p>Define properties using the<code>**@Property**</code> annotation and use jqwik&rsquo;s built-in generators to create input data.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">net.jqwik.api.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ExamplePropertyTest</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="nd">@Property</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="kt">boolean</span><span class="w"/><span class="nf">concatenationLength</span><span class="p">(</span><span class="nd">@ForAll</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">a</span><span class="p">,</span><span class="w"/><span class="nd">@ForAll</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">b</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">             </span><span class="k">return</span><span class="w"/><span class="p">(</span><span class="n">a</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">b</span><span class="p">).</span><span class="na">length</span><span class="p">()</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">a</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">b</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">         </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">     </span><span class="p">}</span></span></span></code></pre></div></div><p><strong>Running the Tests</strong> :</p><p>Execute the tests using your preferred method, such as running through an IDE that supports JUnit 5 or using Maven:<strong>mvn test</strong></p><p><strong>Interpreting Results</strong> :</p><p>The framework automatically generates a wide range of inputs to test the property. If a property fails, jqwik attempts to shrink the failing input to a minimal case.</p><h3 id="benefits-of-property-based-testing">Benefits of Property-Based Testing</h3><p><strong>Increased Coverage</strong> : Automatically generates a large number of test cases, covering more scenarios and edge cases.</p><p><strong>Uncovering Edge Cases</strong> : Helps find edge cases that might not be considered during example-based testing.</p><p><strong>Less Manual Work</strong> : Reduces the need to manually write extensive individual test cases.</p><p><strong>Better Specifications</strong> : Encourages writing more general and robust specifications of program behaviour.</p><h3 id="challenges-and-best-practices">Challenges and Best Practices</h3><p><strong>Defining Good Properties</strong> : One of the main challenges is defining general and meaningful properties. These properties should capture the code&rsquo;s essential invariants and behaviours.</p><p><strong>Handling Complex Input Data</strong> : It may be necessary to write custom generators and shrinkers for complex input data.</p><p><strong>Balancing Between Unit and Property-Based Tests</strong> : Property-based testing is robust but should complement rather than replace traditional unit tests. Unit tests are still useful for specific scenarios and regression testing.</p><p>Property-based testing in Java provides a powerful tool for verifying that code behaves correctly across a wide range of inputs. Defineting properties and using automated input generation helps uncover bugs and edge cases that might be missed by traditional testing methods. While there are challenges in defining good properties and handling complex inputs, the benefits of increased coverage and robustness make it a valuable addition to any testing strategy.</p><h2 id="mutation-based-testing-coverage">Mutation-Based Testing Coverage</h2><p>Mutation testing is a technique used to evaluate the quality of software tests. It involves modifying a program&rsquo;s source code in small ways, called &ldquo;mutants,&rdquo; and then running the test suite to see if the tests detect these changes. A good test suite should be able to detect and fail when these mutants are introduced, indicating that the tests are effective in catching errors.</p><h3 id="key-concepts-of-mutation-testing">Key Concepts of Mutation Testing</h3><p><strong>Mutants</strong> : Variations of the original program created by making small changes, such as altering operators, changing constants, or modifying control flow.</p><p><strong>Mutation Operators</strong> : Specific rules or patterns used to create mutants. Examples include:</p><p><strong>Arithmetic Operator Replacement (AOR)</strong> : Replaces arithmetic operators like<code>+</code> with<code>-</code>.</p><p><strong>Logical Operator Replacement (LOR)</strong> : This function replaces logical operators like<code>&amp;&amp;</code> with<code>||</code>.</p><p><strong>Relational Operator Replacement (ROR)</strong> : Replaces relational operators like<code>&gt;</code> with<code>&lt;</code>.</p><p><strong>Mutation Score</strong> : The percentage of mutants that are detected (killed) by the test suite.</p><h3 id="steps-in-mutation-testing">Steps in Mutation Testing</h3><p><strong>Generate Mutants</strong> : Apply mutation operators to the original source code to create a set of mutant programs.</p><p><strong>Run Tests</strong> : Execute the test suite on each mutant.</p><p><strong>Analyse Results</strong> : Determine if the tests pass or fail for each mutant. If a test fails, the mutant is considered &ldquo;killed.&rdquo; If all tests pass, the mutant is considered &ldquo;survived.&rdquo;</p><h3 id="pitest-for-mutation-testing-in-java">Pitest for Mutation Testing in Java</h3><p>A widely used mutation testing tool for Java that integrates with various build tools and CI/CD pipelines.</p><p><strong>Adding PIT to Your Project</strong></p><p>Add the PIT plugin to your<code>pom.xml</code>:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;plugin&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;groupId&gt;</span>org.pitest<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;artifactId&gt;</span>pitest-maven<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;version&gt;</span>1.6.9<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;executions&gt;</span></span></span><span class="line"><span class="cl">         <span class="nt">&lt;execution&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;goals&gt;</span></span></span><span class="line"><span class="cl">             <span class="nt">&lt;goal&gt;</span>mutationCoverage<span class="nt">&lt;/goal&gt;</span></span></span><span class="line"><span class="cl">           <span class="nt">&lt;/goals&gt;</span></span></span><span class="line"><span class="cl">         <span class="nt">&lt;/execution&gt;</span></span></span><span class="line"><span class="cl">       <span class="nt">&lt;/executions&gt;</span></span></span><span class="line"><span class="cl">     <span class="nt">&lt;/plugin&gt;</span></span></span></code></pre></div></div><p><strong>Running Mutation Tests</strong></p><p>Run the mutation tests using:<strong>mvn org.pitest:pitest-maven:mutationCoverage</strong></p><p><strong>Interpreting Results</strong></p><p>PIT generates a detailed HTML report showing the mutation score, which mutants were killed, and which survived. The report helps identify weak spots in your test suite.</p><h3 id="example-of-mutation-testing">Example of Mutation Testing</h3><p>Consider a simple Java class and its test:</p><p><strong>Calculator.java</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Calculator</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="nf">add</span><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">a</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">b</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">return</span><span class="w"/><span class="n">a</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">b</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>CalculatorTest.java</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import static</span><span class="w"/><span class="nn">org.junit.jupiter.api.Assertions.assertEquals</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.junit.jupiter.api.Test</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">CalculatorTest</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nd">@Test</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">testAdd</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Calculator</span><span class="w"/><span class="n">calc</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Calculator</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">assertEquals</span><span class="p">(</span><span class="n">5</span><span class="p">,</span><span class="w"/><span class="n">calc</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">2</span><span class="p">,</span><span class="w"/><span class="n">3</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>When running mutation testing on this example, a typical mutant might change the addition operator to subtraction:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="nf">add</span><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">a</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">b</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="k">return</span><span class="w"/><span class="n">a</span><span class="w"/><span class="o">-</span><span class="w"/><span class="n">b</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>If the test suite is adequate, it will fail for this mutant, thus killing it. The test suite might need improvement to cover this scenario if it doesn&rsquo;t.</p><h3 id="benefits-of-mutation-testing">Benefits of Mutation Testing</h3><p><strong>Improves Test Quality</strong> : Helps identify weaknesses in the test suite and areas where tests might be missing.</p><p><strong>Comprehensive Coverage</strong> : Provides a more rigorous evaluation of test effectiveness compared to code coverage metrics alone.</p><p><strong>Identifies Redundant Tests</strong> : Helps detect tests that do not contribute to detecting faults in the code.</p><h3 id="challenges-and-best-practices-1">Challenges and Best Practices</h3><p><strong>Performance Overhead</strong> : Mutation testing can be time-consuming, especially for large codebases, as it requires running the test suite multiple times.</p><p><strong>Equivalent Mutants</strong> : Some mutants may be logically equivalent to the original code and cannot be killed by any test, which can be challenging to identify and handle.</p><p><strong>Selective Mutation Testing</strong> : To reduce the performance overhead, focus on critical parts of the codebase or use a subset of mutation operators.</p><p>Mutation testing is a powerful technique for assessing the effectiveness of your test suite by introducing small changes (mutants) to the code and checking if the tests detect these changes. Tools like PIT make it practical to integrate mutation testing into Java projects, providing valuable insights into test quality and helping to improve the robustness of your tests. By complementing traditional testing methods with mutation testing, you can achieve higher confidence in the correctness and reliability of your software.</p><h2 id="conclusion">Conclusion</h2><p>Each coverage type has its strengths and weaknesses, making them suitable for different aspects of software testing:</p><p><strong>Line Coverage</strong> is useful for ensuring that all parts of the code are executed, but should be complemented with other methods to ensure test quality.</p><p><strong>Property-Based Coverage</strong> excels at testing the code&rsquo;s behaviour over a wide range of inputs and can uncover edge cases that traditional testing might miss.</p><p><strong>Mutation-Based Coverage</strong> provides a rigorous assessment of test suite effectiveness by ensuring that tests can detect intentional faults, though it is resource-intensive.</p><p>For a comprehensive testing strategy, it is beneficial to combine these approaches, leveraging the strengths of each to ensure both thorough and practical testing.</p>
]]></content:encoded><category>Java</category><category>Mutation Testing</category><category>TDD</category><media:content url="https://svenruppert.com/images/2024/05/3E049BD7-65C8-4FBE-86AD-8BA23AFA6F50.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/05/3E049BD7-65C8-4FBE-86AD-8BA23AFA6F50.jpg"/><enclosure url="https://svenruppert.com/images/2024/05/3E049BD7-65C8-4FBE-86AD-8BA23AFA6F50.jpg" type="image/jpeg" length="0"/></item><item><title>Securing Apache Maven: Understanding Cache-Related Risks</title><link>https://svenruppert.com/posts/securing-apache-maven-understanding-cache-related-risks/</link><pubDate>Mon, 27 May 2024 14:30:22 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/securing-apache-maven-understanding-cache-related-risks/</guid><description>What is a Package Manager - Bird-Eye View A package manager is a tool or system in software development designed to simplify the process of installing, updating, configuring, and removing software packages on a computer system. It automates managing dependencies and resolving conflicts between different software components, making it easier for developers to work with various libraries, frameworks, and tools within their projects.</description><content:encoded>&lt;![CDATA[<h2 id="what-is-a-package-manager---bird-eye-view">What is a Package Manager - Bird-Eye View</h2><p>A package manager is a tool or system in software development designed to simplify the process of installing, updating, configuring, and removing software packages on a computer system. It automates managing dependencies and resolving conflicts between different software components, making it easier for developers to work with various libraries, frameworks, and tools within their projects.</p><p>Package managers typically provide a centralised repository or repositories where software packages are hosted. Users can then use the package manager to search for, download, and install the desired packages and any necessary dependencies directly from these repositories.</p><ol><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#what-is-a-package-manager-bird-eye-view">What is a Package Manager - Bird-Eye View</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#what-are-the-pros-and-cons-of-package-managers">What are the pros and cons of Package Managers?</a><ol><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#pros">Pros:</a><ol><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#simplified-installation">Simplified Installation:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#dependency-management">Dependency Management:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#version-control">Version Control:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#centralised-repository">Centralised Repository:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#consistency">Consistency:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#cons">Cons:</a><ol><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#limited-control">Limited Control:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#security-risks">Security Risks:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#versioning-issues">Versioning Issues:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#performance-overhead">Performance Overhead:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#dependency-bloat">Dependency Bloat:</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#what-is-maven">What is Maven?</a><ol><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#project-object-model-pom">Project Object Model (POM):</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#dependency-management">Dependency Management:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#convention-over-configuration">Convention over Configuration:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#build-lifecycle">Build Lifecycle:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#plugins">Plugins:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#central-repository">Central Repository:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#integration-with-ides">Integration with IDEs:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#transparency-and-repeatability">Transparency and Repeatability:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#what-are-typical-securit-risks-using-maven">What are typical Securit Risks using Maven?</a><ol><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#dependency-vulnerabilities">Dependency vulnerabilities:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#malicious-dependencies">Malicious dependencies:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#repository-compromise">Repository compromise:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#man-in-the-middle-attacks">Man-in-the-middle attacks:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#build-script-injection">Build script injection:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#how-does-the-dependency-resolving-mechanism-work-in-maven">How does the dependency-resolving mechanism work in Maven?</a><ol><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#dependency-declaration">Dependency Declaration:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#dependency-tree">Dependency Tree:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#repository-resolution">Repository Resolution:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#dependency-download">Dependency Download:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#version-conflict-resolution">Version Conflict Resolution:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#transitive-dependency-resolution">Transitive Dependency Resolution:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#dependency-caching">Dependency Caching:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#dependency-scope">Dependency Scope:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#what-attacks-target-maven-s-cache-structure">What attacks target Maven&rsquo;s cache structure?</a><ol><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#cache-poisoning">Cache Poisoning:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#cache-exfiltration">Cache Exfiltration:</a></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#cache-manipulation">Cache Manipulation:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/27/securing-apache-maven-understanding-cache-related-risks/#conclusion">Conclusion:</a></li></ol><p>Some popular package managers include:</p><p>1.<strong>APT (Advanced Package Tool)</strong> : Used primarily in Debian-based Linux distributions such as Ubuntu, APT simplifies installing and managing software packages.</p><p>2.<strong>YUM (Yellowdog Updater Modified)</strong> and<strong>DNF (Dandified YUM)</strong> : Package managers commonly used in Red Hat-based Linux distributions like Fedora and CentOS.</p><p>3.<strong>Homebrew</strong> : A package manager for macOS and Linux, Homebrew simplifies the installation of software packages and libraries.</p><p>4.<strong>npm (Node Package Manager)</strong> and<strong>Yarn</strong> are package managers for JavaScript and Node.js that manage dependencies in web development projects.</p><p>5.<strong>pip</strong> : The package installer for Python, allowing developers to easily install Python libraries and packages from the Python Package Index (PyPI) repository.</p><p>6.<strong>Composer:</strong> is a dependency manager for PHP, used to manage libraries and dependencies in PHP projects.</p><p>Package managers greatly simplify software development by automating the process of installing and managing software dependencies. This reduces errors, improves efficiency, and facilitates collaboration among developers.</p><h2 id="what-are-the-pros-and-cons-of-package-managers">What are the pros and cons of Package Managers?</h2><p>Package managers offer numerous benefits to software developers and users alike, but they also have drawbacks. Here are the pros and cons of using a package manager:</p><h3 id="pros">Pros:</h3><h4 id="simplified-installation-"><strong>Simplified Installation</strong> :</h4><p>Package managers automate the process of installing software packages, making it straightforward for users to add new software to their systems.</p><h4 id="dependency-management">Dependency Management:</h4><p>Package managers handle dependencies automatically, ensuring all required libraries and components are installed correctly. This reduces the likelihood of dependency conflicts and makes it easier to manage complex software ecosystems.</p><h4 id="version-control">Version Control:</h4><p>Package managers keep track of software versions and updates, allowing users to upgrade to newer versions when they become available quickly. This helps ensure that software remains up-to-date and secure.</p><h4 id="centralised-repository">Centralised Repository:</h4><p>Package managers typically provide access to a centralised repository of software packages, making it easy for users to discover new software and libraries.</p><h4 id="consistency">Consistency:</h4><p>Package managers enforce consistency across different environments by ensuring that all installations are performed in a standardised manner. This reduces the likelihood of configuration errors and compatibility issues.</p><h3 id="cons">Cons:</h3><h4 id="limited-control">Limited Control:</h4><p>Package managers abstract away many of the details of software installation and configuration, which can sometimes limit users&rsquo; control over the installation process. Advanced users may prefer more manual control over their software installations.</p><h4 id="security-risks">Security Risks:</h4><p>Because package managers rely on centralised repositories, malicious actors could compromise them and distribute malicious software packages. Users must trust the integrity of the repository and the software packages hosted within it.</p><h4 id="versioning-issues">Versioning Issues:</h4><p>Dependency management can sometimes lead to versioning issues, primarily when multiple software packages depend on conflicting versions of the same library. Resolving these conflicts can be challenging and may require manual intervention.</p><h4 id="performance-overhead">Performance Overhead:</h4><p>Package managers introduce a performance overhead, as they must download and install software packages and their dependencies. In some cases, this overhead may be negligible, but it can become a concern for large projects or systems with strict performance requirements.</p><h4 id="dependency-bloat">Dependency Bloat:</h4><p>Dependency management can sometimes lead to &ldquo;dependency bloat,&rdquo; where software projects rely on a large number of external libraries and components. This can increase the size of software installations and introduce additional maintenance overhead.</p><p>While package managers offer significant benefits in simplifying software installation and dependency management, users must be aware of the potential drawbacks and trade-offs involved.</p><h2 id="what-is-maven">What is Maven?</h2><p>Apache Maven is a powerful build automation and dependency management tool primarily used for Java projects. However, it can also manage projects in other languages like C#, Ruby, and Scala. It provides a consistent and standardised way to build, test, and deploy software projects. Here are some critical details about Maven:</p><h3 id="project-object-model-pom">Project Object Model (POM):</h3><p>Maven uses a Project Object Model (POM) to describe a project&rsquo;s structure and configuration. The POM is an XML file that contains information about the project&rsquo;s dependencies, build settings, plugins, and other metadata. It serves as the central configuration file for the project and is used by Maven to automate various tasks.</p><h3 id="dependency-management-1">Dependency Management:</h3><p>One of Maven&rsquo;s key features is its robust dependency management system. Dependencies are specified in the POM file, and Maven automatically downloads the required libraries from remote repositories such as Maven Central. Maven also handles transitive dependencies, automatically resolving and downloading dependencies required by other dependencies.</p><h3 id="convention-over-configuration">Convention over Configuration:</h3><p>Maven follows the &ldquo;convention over configuration&rdquo; principle, which encourages standard project structures and naming conventions. By adhering to these conventions, Maven can automatically infer settings and reduce the configuration required.</p><h3 id="build-lifecycle">Build Lifecycle:</h3><p>Maven defines a standard build lifecycle consisting of phases: compile, test, package, install, and deploy. Each phase represents a specific stage in the build process, and Maven plugins can be bound to these phases to perform various tasks such as compiling source code, running tests, creating JAR or WAR files, and deploying artefacts.</p><h3 id="plugins">Plugins:</h3><p>Maven is highly extensible through plugins, which provide additional functionality for tasks such as compiling code, generating documentation, and deploying artefacts. Maven plugins can be either built-in or custom-developed, and they can be configured in the POM file to customise the build process.</p><h3 id="central-repository">Central Repository:</h3><p>Maven Central is the default repository for hosting Java libraries and artefacts. It contains a vast collection of open-source and third-party libraries that can be easily referenced in Maven projects. Additionally, organisations and individuals can set up their own repositories to host proprietary or custom libraries.</p><h3 id="integration-with-ides">Integration with IDEs:</h3><p>Maven integrates seamlessly with popular Integrated Development Environments (IDEs) such as Eclipse, IntelliJ IDEA, and NetBeans. IDEs typically provide Maven support through plugins, allowing developers to import Maven projects, manage dependencies, and run Maven goals directly from the IDE.</p><h3 id="transparency-and-repeatability">Transparency and Repeatability:</h3><p>Maven uses declarative configuration and standardised project structures to promote transparency and repeatability in the build process. This makes it easier for developers to understand and reproduce builds across different environments.</p><p>Overall, Apache Maven is a versatile and widely used tool in the Java ecosystem. It offers powerful features for automating build processes, managing dependencies, and promoting best practices in software development. Its adoption by numerous open-source projects and enterprises underscores its importance in modern software development workflows.</p><h2 id="what-are-typical-securit-risks-using-maven">What are typical Securit Risks using Maven?</h2><p>While Apache Maven is a widely used and generally secure tool for managing dependencies and building Java projects, there are some potential security risks associated with its usage:</p><h3 id="dependency-vulnerabilities">Dependency vulnerabilities:</h3><p>One of the leading security risks with Maven (as with any dependency management system) is the possibility of including dependencies with known vulnerabilities. If a project relies on a library with a security flaw, it could expose the application to various security risks, including remote code execution, data breaches, and denial-of-service attacks. It&rsquo;s essential to update dependencies to patched versions regularly and use tools like OWASP Dependency-Check to identify and mitigate vulnerabilities.</p><h3 id="malicious-dependencies">Malicious dependencies:</h3><p>While Maven Central and other reputable repositories have strict guidelines for publishing artefacts, there is still a risk of including malicious or compromised dependencies in a project. Attackers could compromise a legitimate library or create a fake library with malicious code and upload it to a repository. Developers should only use trusted repositories, verify the integrity of dependencies, and review code changes carefully.</p><h3 id="repository-compromise">Repository compromise:</h3><p>Maven relies on remote repositories to download dependencies, and if a repository is compromised, it could serve malicious or tampered artefacts to unsuspecting users. While Maven Central has robust security measures, smaller or custom repositories may have weaker security controls. Organisations should implement secure repository management practices, such as using HTTPS for communication, signing artefacts with GPG, and restricting access to trusted users.</p><h3 id="man-in-the-middle-attacks">Man-in-the-middle attacks:</h3><p>Maven communicates with remote repositories over HTTP or HTTPS, and if an attacker intercepts the communication, they could tamper with the downloaded artefacts or inject malicious code into the project. To mitigate this risk, developers should use HTTPS for all repository communications, verify SSL certificates, and consider using tools like Maven&rsquo;s own repository manager or Nexus Repository Manager, which support repository proxies and caching to reduce the reliance on external repositories.</p><h3 id="build-script-injection">Build script injection:</h3><p>Maven&rsquo;s build scripts (POM files) are XML files that define project configurations, dependencies, and build settings. If an attacker gains unauthorised access to a project&rsquo;s source code repository or CI/CD pipeline, they could modify the build script to execute arbitrary commands or introduce vulnerabilities into the build process. Organisations should implement proper access controls, code review processes, and security scanning tools to detect and prevent unauthorised changes to build scripts.</p><p>By understanding these security risks and implementing best practices for secure software development and dependency management, developers can mitigate potential threats and ensure the integrity and security of their Maven-based projects. Regular security audits, vulnerability scanning, and staying informed about security updates and patches are also essential for maintaining a secure development environment.</p><h2 id="how-does-the-dependency-resolving-mechanism-work-in-maven">How does the dependency-resolving mechanism work in Maven?</h2><p>Maven&rsquo;s dependency resolution mechanism is a crucial aspect of its functionality, ensuring that projects have access to the necessary libraries and components while managing conflicts and versioning issues effectively. Here&rsquo;s how the dependency-resolving mechanism works in Maven:</p><h3 id="dependency-declaration">Dependency Declaration:</h3><p>Developers specify dependencies for their projects in the project&rsquo;s POM (Project Object Model) file. Dependencies are declared within the<code>&lt;dependencies&gt;</code> element, where each dependency includes details such as group ID, artifact ID, and version.</p><h3 id="dependency-tree">Dependency Tree:</h3><p>Maven constructs a dependency tree based on the declared dependencies in the POM file. This tree represents the hierarchical structure of dependencies, including direct dependencies (specified in the POM file) and transitive dependencies (dependencies required by other dependencies).</p><h3 id="repository-resolution">Repository Resolution:</h3><p>When a build is initiated, Maven attempts to resolve dependencies by searching for them in configured repositories. By default, Maven Central is the primary repository, but developers can specify additional repositories in the POM file or through Maven settings.</p><h3 id="dependency-download">Dependency Download:</h3><p>If a dependency is not already present in the local Maven repository, Maven downloads it from the remote repository where it&rsquo;s hosted. Maven stores downloaded dependencies in the local repository (<code>~/.m2/repository</code> by default) for future reuse.</p><h3 id="version-conflict-resolution">Version Conflict Resolution:</h3><p>Maven employs a strategy for resolving version conflicts when multiple dependencies require different versions of the same library. By default, Maven uses the &ldquo;nearest-wins&rdquo; strategy, where the version closest to the project in the dependency tree takes precedence. Developers can explicitly specify versions for dependencies to override the default resolution behaviour. Maven also provides options such as dependency management and exclusions to manage version conflicts better.</p><h3 id="transitive-dependency-resolution">Transitive Dependency Resolution:</h3><p>Maven automatically resolves transitive dependencies, ensuring all required libraries and components are included in the project&rsquo;s classpath. Maven traverses the dependency tree recursively, downloading and including transitive dependencies as needed.</p><h3 id="dependency-caching">Dependency Caching:</h3><p>Maven caches downloaded dependencies in the local repository to improve build performance and reduce network traffic. Subsequent builds reuse cached dependencies whenever possible, avoiding redundant downloads.</p><h3 id="dependency-scope">Dependency Scope:</h3><p>Maven supports different dependency scopes (e.g., compile, test, runtime) to control the visibility and usage of dependencies during various phases of the build lifecycle. Scopes help prevent unnecessary dependencies in production builds and improve build efficiency.</p><p>Overall, Maven&rsquo;s dependency resolution mechanism simplifies the management of project dependencies by automating the process of downloading, organizing, and resolving dependencies. This allows developers to focus on writing code rather than manually managing library dependencies.</p><h2 id="what-attacks-target-mavens-cache-structure">What attacks target Maven&rsquo;s cache structure?</h2><p>Attacks targeting the cache structure of Maven, particularly the local repository cache (<code>~/.m2/repository</code>), are relatively rare but can potentially exploit vulnerabilities in the caching mechanism to compromise the integrity or security of Maven-based projects. Here are some potential attacks that could target Maven&rsquo;s cache structure:</p><h3 id="cache-poisoning">Cache Poisoning:</h3><p><strong>Description</strong> : Cache poisoning attacks involve manipulating the contents of the local repository cache to introduce malicious artefacts or modified versions of legitimate artefacts. Once poisoned, subsequent builds may unwittingly use the compromised artefacts, leading to security vulnerabilities or system compromise.</p><p><strong>Attack Vector</strong> : Attackers may exploit vulnerabilities in Maven&rsquo;s caching mechanism, such as improper input validation or insecure handling of cached artefacts, to inject malicious artefacts into the cache.</p><p><strong>Mitigation</strong> : To mitigate cache poisoning attacks, Maven users should regularly verify the integrity of cached artefacts using checksums or digital signatures. Employing secure repository management practices, such as signing artefacts with GPG and enabling repository managers with artefact validation, can also enhance security.</p><h3 id="cache-exfiltration">Cache Exfiltration:</h3><p><strong>Description</strong> : Cache exfiltration attacks involve an attacker extracting sensitive information from the local repository cache. This could include credentials, private keys, or other confidential data inadvertently stored within cached artefacts or metadata.</p><p><strong>Attack Vector</strong> : Attackers may exploit vulnerabilities in Maven or its plugins to access and extract sensitive information stored in the local repository cache. For example, a compromised plugin could inadvertently leak credentials stored in Maven settings files.</p><p><strong>Mitigation</strong> : To mitigate cache exfiltration attacks, developers should avoid storing sensitive information in Maven configuration files or cached artefacts. Instead, they should use secure credential management practices, such as environment variables or encrypted credential stores, to prevent the inadvertent exposure of sensitive data.</p><h3 id="cache-manipulation">Cache Manipulation:</h3><p><strong>Description</strong> : Cache manipulation attacks involve modifying the contents of the local repository cache to alter the behaviour of Maven builds or compromise the integrity of projects. To achieve their objectives, attackers may tamper with cached artefacts, metadata, or configuration files.</p><p><strong>Attack Vector</strong> : Attackers may exploit vulnerabilities in Maven or its dependencies to manipulate the local repository cache. For example, an attacker could modify cached artefacts to include backdoors or malware.</p><p><strong>Mitigation</strong> : To mitigate cache manipulation attacks, developers should ensure that the local repository cache is stored in a secure location with restricted access permissions. They should also regularly verify the integrity of cached artifacts and configuration files using checksums or digital signatures. Implementing secure software development practices, such as code reviews and vulnerability scanning, can also help detect and prevent cache manipulation attacks.</p><p>While attacks targeting Maven&rsquo;s cache structure are relatively uncommon, developers should remain vigilant and implement security best practices to safeguard against potential vulnerabilities and threats. Regularly updating Maven and its dependencies to the latest versions and maintaining awareness of security advisories and patches is essential for mitigating the risk of cache-related attacks.</p><h2 id="conclusion">Conclusion:</h2><p>In conclusion, while attacks explicitly targeting Maven&rsquo;s cache structure are relatively rare, they pose potential risks to the integrity and security of Maven-based projects. Cache poisoning, cache exfiltration, and cache manipulation are possible attack vectors that attackers may exploit to compromise Maven&rsquo;s local repository cache (<code>~/.m2/repository</code>).</p><p>To mitigate these risks, developers and organisations should adopt robust security measures and best practices:</p><p><strong>Regularly Verify Artifact Integrity</strong> : Employ mechanisms such as checksums or digital signatures to verify the integrity of cached artefacts and ensure they haven&rsquo;t been tampered with.</p><p><strong>Secure Credential Management</strong> : Avoid storing sensitive information, such as credentials or private keys, in Maven configuration files or cached artefacts. Instead, use secure credential management practices, such as environment variables or encrypted stores.</p><p><strong>Access Control and Permissions</strong> : Ensure the local repository cache is stored securely with restricted access permissions to prevent unauthorised access or manipulation.</p><p><strong>Update and Patch</strong> : Regularly update Maven and its dependencies to the latest versions to mitigate potential vulnerabilities. Stay informed about security advisories and apply patches promptly.</p><p><strong>Secure Repository Management</strong> : Implement secure repository management practices, including signing artefacts with GPG, enabling repository managers with artefact validation, and using HTTPS for repository communication.</p><p>By implementing these security measures and remaining vigilant, developers can reduce the likelihood of cache-related attacks and enhance the overall security posture of Maven-based projects. Additionally, fostering a culture of security awareness and education within development teams can help mitigate risks and respond effectively to emerging threats in the software development lifecycle.</p>
]]></content:encoded><category>Java</category><category>Security</category><category>TDD</category><media:content url="https://svenruppert.com/images/2024/05/8F0D7E69-ACAA-4EFD-890F-55DAB1313F9F.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/05/8F0D7E69-ACAA-4EFD-890F-55DAB1313F9F.jpg"/><enclosure url="https://svenruppert.com/images/2024/05/8F0D7E69-ACAA-4EFD-890F-55DAB1313F9F.jpg" type="image/jpeg" length="0"/></item><item><title>CWE-22: Best practices to use Java NIO</title><link>https://svenruppert.com/posts/cwe-22-best-practices-to-use-java-nio/</link><pubDate>Wed, 22 May 2024 10:30:27 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/cwe-22-best-practices-to-use-java-nio/</guid><description>In today&rsquo;s digital landscape, ensuring the security of your applications is paramount. One critical vulnerability developers must guard against is CWE-22, Path Traversal. This vulnerability can allow attackers to access files and directories outside the intended scope, potentially leading to unauthorised access and data breaches.</description><content:encoded>&lt;![CDATA[<p>In today&rsquo;s digital landscape, ensuring the security of your applications is paramount. One critical vulnerability developers must guard against is CWE-22, Path Traversal. This vulnerability can allow attackers to access files and directories outside the intended scope, potentially leading to unauthorised access and data breaches.</p><p>Java&rsquo;s New I/O (NIO) package provides robust tools for file and path manipulation, which can be effectively leveraged to mitigate the risks associated with CWE-22. This blog post will explore best practices for using Java NIO to prevent path traversal vulnerabilities. From proper path normalisation to secure file handling techniques, we&rsquo;ll cover everything you need to know to enhance the security of your Java applications.</p><ol><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#normalise-paths">Normalise Paths</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#validate-path-to-ensure-it-stays-within-a-base-directory">Validate Path to Ensure It Stays Within a Base Directory</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#use-secure-directory-and-file-permissions">Use Secure Directory and File Permissions</a><ol><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#setting-permissions-for-a-directory">Setting Permissions for a Directory</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#setting-permissions-for-files">Setting Permissions for Files</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#checking-file-permissions">Checking File Permissions</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#practical-security-considerations">Practical Security Considerations</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#handle-symlinks-securely">Handle Symlinks Securely</a><ol><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#example-avoid-following-symlinks">Example: Avoid Following Symlinks</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#example-validate-the-target-of-symlink">Example: Validate the Target of Symlink</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#practical-security-considerations">Practical Security Considerations</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#validate-user-inputs-rigorously">Validate User Inputs Rigorously</a><ol><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#example-implementation">Example Implementation</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#implement-logging-and-monitoring">Implement Logging and Monitoring</a><ol><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#detecting-suspicious-activity">Detecting Suspicious Activity</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#incident-response-and-forensics">Incident Response and Forensics</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#accountability-and-compliance">Accountability and Compliance</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#proactive-threat-detection">Proactive Threat Detection</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#improving-security-posture">Improving Security Posture</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#real-time-monitoring">Real-Time Monitoring</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#use-securerandom-for-temporary-files">Use SecureRandom for Temporary Files</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#use-filesystems-for-consistent-path-handling">Use FileSystems for Consistent Path Handling</a><ol><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#key-concepts">Key Concepts</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#best-practices">Best Practices</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-best-practices-to-use-java-nio/#example-implementation">Example Implementation</a></li></ol></li></ol><h3 id="normalise-paths">Normalise Paths</h3><p>Normalisation eliminates any redundant elements from a path, such as<code>.</code> (current directory) and<code>..</code> (parent directory). This process helps ensure that paths are appropriately structured and prevents path traversal attacks.</p><p><strong>Create the Base Path</strong> :</p><p>Define the base directory against which the user input will be resolved. This should be the directory within which you want to restrict access.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="o">**</span><span class="n">Path</span><span class="w"/><span class="n">basePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"/var/www/uploads"</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="o">**</span></span></span></code></pre></div></div><p><strong>Resolve the User Input</strong> :</p><p>Combine the base path with the user-provided input to create a complete path. This step is crucial to ensure that any relative paths the user provides are interpreted in the context of the base path.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"/><span class="n">userInput</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="s">"file"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">resolvedPath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">basePath</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">userInput</span><span class="p">);</span></span></span></code></pre></div></div><p><strong>Normalise the Resolved Path</strong> :</p><p>Normalise the resolved path to eliminate any redundant elements. This step ensures that any<code>.</code> or<code>..</code> in the path are correctly resolved.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="o">**</span><span class="n">Path</span><span class="w"/><span class="n">normalizedPath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">resolvedPath</span><span class="p">.</span><span class="na">normalize</span><span class="p">();</span><span class="o">**</span></span></span></code></pre></div></div><p><strong>Validate the Normalized Path</strong> :</p><p>Ensure that the normalised path starts with the base path. This check ensures that the path does not traverse outside the intended directory.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">normalizedPath</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="n">basePath</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecurityException</span><span class="p">(</span><span class="s">"Invalid file path: path traversal attempt detected."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="validate-path-to-ensure-it-stays-within-a-base-directory">Validate Path to Ensure It Stays Within a Base Directory</h3><p><strong>Normalise the Base Path</strong> :</p><p>Ensure that the base directory is normalised to its simplest form.</p><p><strong>Resolve and Normalize the User-Provided Path</strong> :</p><p>Combine the base directory with the user-provided path, then normalise the resultant path.</p><p><strong>Check if the Normalized Path Starts with the Base Path</strong> :</p><p>Ensure the final normalised path starts with the base directory to confirm it hasn&rsquo;t traversed outside the intended directory.</p><h3 id="use-secure-directory-and-file-permissions">Use Secure Directory and File Permissions</h3><p>Using secure directory and file permissions is essential to ensuring your files&rsquo; and directories&rsquo; safety and integrity, especially when dealing with user-provided inputs in Java applications.</p><p>Java NIO provides APIs for setting and checking file permissions. The<code>PosixFilePermissions</code> and<code>PosixFileAttributeView</code> classes can do this.</p><h4 id="setting-permissions-for-a-directory">Setting Permissions for a Directory</h4><p>To set permissions for a directory, you can use<code>Files.createDirectories</code> and<code>PosixFilePermissions</code> to specify the desired permissions:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.nio.file.*;</span></span><span class="line"><span class="cl">import java.nio.file.attribute.PosixFilePermission;</span></span><span class="line"><span class="cl">import java.nio.file.attribute.PosixFilePermissions;</span></span><span class="line"><span class="cl">import java.util.Set;</span></span><span class="line"><span class="cl">import java.io.IOException;</span></span><span class="line"><span class="cl">public class SecureFileHandler {</span></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Creates a directory with secure permissions.</span></span><span class="line"><span class="cl"> *</span></span><span class="line"><span class="cl"> * @param dirPath The path of the directory to create.</span></span><span class="line"><span class="cl"> * @throws IOException if an I/O error occurs.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> public static void createSecureDirectory(Path dirPath) throws IOException {</span></span><span class="line"><span class="cl"> Set<span class="nt">&lt;PosixFilePermission&gt;</span> perms = PosixFilePermissions.fromString("rwxr-x---");</span></span><span class="line"><span class="cl"> FileAttribute<span class="nt">&lt;Set</span><span class="err">&lt;PosixFilePermission</span><span class="nt">&gt;</span>&gt; attr = PosixFilePermissions.asFileAttribute(perms);</span></span><span class="line"><span class="cl"> Files.createDirectories(dirPath, attr);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Example usage of creating a secure directory.</span></span><span class="line"><span class="cl"> *</span></span><span class="line"><span class="cl"> * @param args Command line arguments.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> public static void main(String[] args) {</span></span><span class="line"><span class="cl"> Path dirPath = Paths.get("/var/www/uploads");</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> createSecureDirectory(dirPath);</span></span><span class="line"><span class="cl"> } catch (IOException e) {</span></span><span class="line"><span class="cl"> e.printStackTrace();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h4 id="setting-permissions-for-files">Setting Permissions for Files</h4><p>Similarly, you can set permissions for files using the<code>Files.setPosixFilePermissions</code> method:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.nio.file.*;</span></span><span class="line"><span class="cl">import java.nio.file.attribute.PosixFilePermission;</span></span><span class="line"><span class="cl">import java.nio.file.attribute.PosixFilePermissions;</span></span><span class="line"><span class="cl">import java.io.IOException;</span></span><span class="line"><span class="cl">import java.util.Set;</span></span><span class="line"><span class="cl">public class SecureFileHandler {</span></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Sets secure permissions for a file.</span></span><span class="line"><span class="cl"> *</span></span><span class="line"><span class="cl"> * @param filePath The path of the file.</span></span><span class="line"><span class="cl"> * @throws IOException if an I/O error occurs.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> public static void setSecureFilePermissions(Path filePath) throws IOException {</span></span><span class="line"><span class="cl"> Set<span class="nt">&lt;PosixFilePermission&gt;</span> perms = PosixFilePermissions.fromString("rw-r-----");</span></span><span class="line"><span class="cl"> Files.setPosixFilePermissions(filePath, perms);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Example usage of setting secure file permissions.</span></span><span class="line"><span class="cl"> *</span></span><span class="line"><span class="cl"> * @param args Command line arguments.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> public static void main(String[] args) {</span></span><span class="line"><span class="cl"> Path filePath = Paths.get("/var/www/uploads/example.txt");</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> setSecureFilePermissions(filePath);</span></span><span class="line"><span class="cl"> } catch (IOException e) {</span></span><span class="line"><span class="cl"> e.printStackTrace();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h4 id="checking-file-permissions">Checking File Permissions</h4><p>Before performing file operations, checking if the file or directory has the correct permissions is important. Here’s how you can do this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.nio.file.*;</span></span><span class="line"><span class="cl">import java.nio.file.attribute.PosixFilePermissions;</span></span><span class="line"><span class="cl">import java.nio.file.attribute.PosixFilePermission;</span></span><span class="line"><span class="cl">import java.io.IOException;</span></span><span class="line"><span class="cl">import java.util.Set;</span></span><span class="line"><span class="cl">public class SecureFileHandler {</span></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Checks if a file has the required permissions.</span></span><span class="line"><span class="cl"> *</span></span><span class="line"><span class="cl"> * @param filePath The path of the file.</span></span><span class="line"><span class="cl"> * @param requiredPerms The required permissions.</span></span><span class="line"><span class="cl"> * @return True if the file has the required permissions, false otherwise.</span></span><span class="line"><span class="cl"> * @throws IOException if an I/O error occurs.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> public static boolean hasRequiredPermissions(Path filePath, Set<span class="nt">&lt;PosixFilePermission&gt;</span> requiredPerms) throws IOException {</span></span><span class="line"><span class="cl"> Set<span class="nt">&lt;PosixFilePermission&gt;</span> perms = Files.getPosixFilePermissions(filePath);</span></span><span class="line"><span class="cl"> return perms.containsAll(requiredPerms);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Example usage of checking file permissions.</span></span><span class="line"><span class="cl"> *</span></span><span class="line"><span class="cl"> * @param args Command line arguments.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> public static void main(String[] args) {</span></span><span class="line"><span class="cl"> Path filePath = Paths.get("/var/www/uploads/example.txt");</span></span><span class="line"><span class="cl"> Set<span class="nt">&lt;PosixFilePermission&gt;</span> requiredPerms = PosixFilePermissions.fromString("rw-r-----");</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> if (hasRequiredPermissions(filePath, requiredPerms)) {</span></span><span class="line"><span class="cl"> System.out.println("File has the required permissions.");</span></span><span class="line"><span class="cl"> } else {</span></span><span class="line"><span class="cl"> System.out.println("File does not have the required permissions.");</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> } catch (IOException e) {</span></span><span class="line"><span class="cl"> e.printStackTrace();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h4 id="practical-security-considerations">Practical Security Considerations</h4><p><strong>Restrict Write Access</strong> :</p><p>Ensure that write access is limited to necessary users and processes only. This minimises the risk of unauthorised modifications.</p><p><strong>Read-Only Access</strong> :</p><p>Set read-only permissions for files that do not need to be modified to prevent unauthorised changes.</p><p><strong>Execute Permissions</strong> :</p><p>Be cautious when granting executing permissions. Only grant execute permissions to necessary users and ensure scripts or executables are secure.</p><p><strong>Owner and Group Permissions</strong> :</p><p>Set appropriate owner and group permissions. Ensure sensitive files and directories are owned by the correct user and group.</p><p><strong>Symbolic Links</strong> :</p><p>Avoid following symbolic links if possible. This can prevent attackers from bypassing security controls using symbolic link attacks.</p><p><strong>Use of umask</strong> :</p><p>Configure the<code>umask</code> value to control the default permission settings for newly created files and directories. This ensures a baseline of security.</p><p>Using Java NIO’s<code>Path</code> and<code>Files</code> classes and proper permission settings can significantly enhance the security of your file and directory operations. These practices help mitigate the risk of unauthorised access and modifications, protecting your applications from potential vulnerabilities related to CWE-22 (Path Traversal).</p><h3 id="handle-symlinks-securely">Handle Symlinks Securely</h3><p>Handling symbolic links (symlinks) securely is crucial to prevent potential security risks such as bypassing access controls and path traversal attacks. Symlinks can be exploited by attackers to gain unauthorised access to files and directories. Here are best practices for securely handling symlinks in Java using the NIO (New I/O) API:</p><p><strong>Avoid Following Symlinks</strong> :</p><p>Use the<code>NOFOLLOW_LINKS</code> option when performing file operations to avoid following symlinks. This ensures operations are performed on the symlink rather than the target file or directory.</p><p><strong>Validate the Target of Symlinks</strong> :</p><p>If your application must follow symlinks, validate the symlink&rsquo;s target to ensure it points to an allowed location.</p><p><strong>Check for Symlinks</strong> :</p><p>Explicitly check if a path is a symlink and handle it accordingly.</p><h4 id="example-avoid-following-symlinks">Example: Avoid Following Symlinks</h4><p>Here’s how you can avoid following symlinks in file operations using Java NIO:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.attribute.BasicFileAttributes</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SecureSymlinkHandler</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * Checks if the given path is a symbolic link.</span></span></span><span class="line"><span class="cl"><span class="cm"> *</span></span></span><span class="line"><span class="cl"><span class="cm"> * @param path The path to check.</span></span></span><span class="line"><span class="cl"><span class="cm"> * @return True if the path is a symbolic link, false otherwise.</span></span></span><span class="line"><span class="cl"><span class="cm"> * @throws IOException if an I/O error occurs.</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isSymlink</span><span class="p">(</span><span class="n">Path</span><span class="w"/><span class="n">path</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">isSymbolicLink</span><span class="p">(</span><span class="n">path</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * Safely deletes a file without following symbolic links.</span></span></span><span class="line"><span class="cl"><span class="cm"> *</span></span></span><span class="line"><span class="cl"><span class="cm"> * @param path The path to the file to delete.</span></span></span><span class="line"><span class="cl"><span class="cm"> * @throws IOException if an I/O error occurs.</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">safeDelete</span><span class="p">(</span><span class="n">Path</span><span class="w"/><span class="n">path</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isSymlink</span><span class="p">(</span><span class="n">path</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecurityException</span><span class="p">(</span><span class="s">"Refusing to delete symbolic link: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">path</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">delete</span><span class="p">(</span><span class="n">path</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * Safely reads a file's attributes without following symbolic links.</span></span></span><span class="line"><span class="cl"><span class="cm"> *</span></span></span><span class="line"><span class="cl"><span class="cm"> * @param path The path to the file.</span></span></span><span class="line"><span class="cl"><span class="cm"> * @return The file's attributes.</span></span></span><span class="line"><span class="cl"><span class="cm"> * @throws IOException if an I/O error occurs.</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">BasicFileAttributes</span><span class="w"/><span class="nf">safeReadAttributes</span><span class="p">(</span><span class="n">Path</span><span class="w"/><span class="n">path</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">readAttributes</span><span class="p">(</span><span class="n">path</span><span class="p">,</span><span class="w"/><span class="n">BasicFileAttributes</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/><span class="n">LinkOption</span><span class="p">.</span><span class="na">NOFOLLOW_LINKS</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">path</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"/var/www/uploads/example.txt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isSymlink</span><span class="p">(</span><span class="n">path</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Path is a symbolic link."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Path is not a symbolic link."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">BasicFileAttributes</span><span class="w"/><span class="n">attrs</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">safeReadAttributes</span><span class="p">(</span><span class="n">path</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"File size: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">attrs</span><span class="p">.</span><span class="na">size</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">safeDelete</span><span class="p">(</span><span class="n">path</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"File deleted safely."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">SecurityException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h4 id="example-validate-the-target-of-symlink">Example: Validate the Target of Symlink</h4><p>If your application needs to follow symlinks, validate their targets to ensure they point to an acceptable location</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SecureSymlinkHandler</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="cm">/**</span></span></span><span class="line"><span class="cl"><span class="cm"> * Validates that the symlink's target is within the allowed base directory.</span></span></span><span class="line"><span class="cl"><span class="cm"> *</span></span></span><span class="line"><span class="cl"><span class="cm"> * @param symlink The symbolic link to validate.</span></span></span><span class="line"><span class="cl"><span class="cm"> * @param baseDir The allowed base directory.</span></span></span><span class="line"><span class="cl"><span class="cm"> * @throws IOException if an I/O error occurs or if validation fails.</span></span></span><span class="line"><span class="cl"><span class="cm"> */</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">validateSymlinkTarget</span><span class="p">(</span><span class="n">Path</span><span class="w"/><span class="n">symlink</span><span class="p">,</span><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">baseDir</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">Files</span><span class="p">.</span><span class="na">isSymbolicLink</span><span class="p">(</span><span class="n">symlink</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Path is not a symbolic link: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">symlink</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">target</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">readSymbolicLink</span><span class="p">(</span><span class="n">symlink</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">resolvedTarget</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">baseDir</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">target</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">resolvedTarget</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="n">baseDir</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecurityException</span><span class="p">(</span><span class="s">"Invalid symlink target: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">resolvedTarget</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">symlink</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"/var/www/uploads/symlink"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">baseDir</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"/var/www/uploads"</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">validateSymlinkTarget</span><span class="p">(</span><span class="n">symlink</span><span class="p">,</span><span class="w"/><span class="n">baseDir</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Symlink target is valid and within the allowed base directory."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">SecurityException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h4 id="practical-security-considerations-1">Practical Security Considerations</h4><p><strong>Restrict Symlink Creation</strong> :</p><p>Only allow trusted users to create symlinks. This minimises the risk of symlinks being used for malicious purposes.</p><p><strong>Regularly Audit Symlinks</strong> :</p><p>Periodically audit symlinks within your application to ensure they are not pointing to unauthorised locations.</p><p><strong>Use Symlink Aware Libraries</strong> :</p><p>Use libraries that are aware of and handle symlinks securely. This can help mitigate the risk of unintentional symlink following.</p><p><strong>Least Privilege Principle</strong> :</p><p>Ensure that your application runs with the minimum required privileges. This reduces the impact of potential symlink-related vulnerabilities.</p><p><strong>Deploy Defense in Depth</strong> :</p><p>Use multiple layers of security controls to protect against symlink attacks. These include filesystem permissions, application-level checks, and regular monitoring.</p><h3 id="validate-user-inputs-rigorously">Validate User Inputs Rigorously</h3><p>Validating user inputs rigorously is crucial to ensure the security and integrity of an application. Proper input validation helps prevent various attacks, including path traversal (CWE-22), SQL injection, cross-site scripting (XSS), and more. Here are some best practices and techniques to rigorously validate user inputs in Java applications:</p><p><strong>Whitelist Validation</strong> : Only allow inputs that match a predefined set of acceptable values. This is the most secure form of validation.</p><p><strong>Blacklist Validation</strong> : Reject inputs that contain known dangerous characters or patterns. This approach is less secure than whitelisting but can be used as an additional measure.</p><p><strong>Length Checks</strong> : Ensure that inputs are within the expected length limits. This prevents buffer overflows and denial of service (DoS) attacks.</p><p><strong>Data Type Checks</strong> : Verify that inputs match the expected data type (e.g., integers, dates).</p><p><strong>Encoding and Escaping</strong> : Encode and escape inputs to prevent injection attacks in different contexts (e.g., HTML, SQL).</p><p><strong>Canonicalisation</strong> : Convert inputs to a standard format before validation. This helps compare and process inputs securely.</p><p><strong>Restrict Allowed Characters</strong> :</p><p>Validate file names and paths against a whitelist of allowed characters. Reject any input containing characters that can alter the path structure (e.g.,<code>..</code>,<code>/</code>,<code>\\</code>).</p><p><strong>Check Path Against Base Directory</strong> :</p><p>Ensure the resolved and normalised path starts with the intended base directory.</p><h4 id="example-implementation">Example Implementation</h4><p>Below is an example implementation in Java that combines these principles and techniques to validate user inputs, specifically for file paths:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.nio.file.*;</span></span><span class="line"><span class="cl">import java.util.Set;</span></span><span class="line"><span class="cl">import java.util.HashSet;</span></span><span class="line"><span class="cl">import java.util.logging.Logger;</span></span><span class="line"><span class="cl">import java.io.IOException;</span></span><span class="line"><span class="cl">import java.util.regex.Pattern;</span></span><span class="line"><span class="cl">public class SecureInputValidator {</span></span><span class="line"><span class="cl"> private static final Logger logger = Logger.getLogger(SecureInputValidator.class.getName());</span></span><span class="line"><span class="cl"> private static final Set<span class="nt">&lt;String&gt;</span> ALLOWED_EXTENSIONS = new HashSet<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> static {</span></span><span class="line"><span class="cl"> ALLOWED_EXTENSIONS.add(".txt");</span></span><span class="line"><span class="cl"> ALLOWED_EXTENSIONS.add(".jpg");</span></span><span class="line"><span class="cl"> ALLOWED_EXTENSIONS.add(".png");</span></span><span class="line"><span class="cl"> ALLOWED_EXTENSIONS.add(".pdf");</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Validates the user-provided file name against a whitelist of allowed characters and extensions.</span></span><span class="line"><span class="cl"> *</span></span><span class="line"><span class="cl"> * @param fileName The user-provided file name.</span></span><span class="line"><span class="cl"> * @throws IllegalArgumentException if the file name is invalid.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> public static void validateFileName(String fileName) throws IllegalArgumentException {</span></span><span class="line"><span class="cl"> if (fileName == null || fileName.isEmpty()) {</span></span><span class="line"><span class="cl"> throw new IllegalArgumentException("File name cannot be null or empty.");</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> // Check for invalid characters</span></span><span class="line"><span class="cl"> Pattern pattern = Pattern.compile("[^a-zA-Z0-9._-]");</span></span><span class="line"><span class="cl"> if (pattern.matcher(fileName).find()) {</span></span><span class="line"><span class="cl"> throw new IllegalArgumentException("File name contains invalid characters.");</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> // Check for allowed file extensions</span></span><span class="line"><span class="cl"> boolean validExtension = ALLOWED_EXTENSIONS.stream().anyMatch(fileName::endsWith);</span></span><span class="line"><span class="cl"> if (!validExtension) {</span></span><span class="line"><span class="cl"> throw new IllegalArgumentException("File extension is not allowed.");</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Validates the user-provided path to ensure it stays within the base directory.</span></span><span class="line"><span class="cl"> *</span></span><span class="line"><span class="cl"> * @param baseDir The base directory.</span></span><span class="line"><span class="cl"> * @param userInput The user-provided input.</span></span><span class="line"><span class="cl"> * @return The validated and normalized path.</span></span><span class="line"><span class="cl"> * @throws SecurityException if a path traversal attempt is detected.</span></span><span class="line"><span class="cl"> * @throws IllegalArgumentException if the file name is invalid.</span></span><span class="line"><span class="cl"> * @throws IOException if an I/O error occurs.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> public static Path getSecureFilePath(String baseDir, String userInput) throws SecurityException, IllegalArgumentException, IOException {</span></span><span class="line"><span class="cl"> validateFileName(userInput);</span></span><span class="line"><span class="cl"> // Normalize the base directory</span></span><span class="line"><span class="cl"> Path basePath = Paths.get(baseDir).normalize();</span></span><span class="line"><span class="cl"> // Resolve the user input against the base directory and normalize the result</span></span><span class="line"><span class="cl"> Path resolvedPath = basePath.resolve(userInput).normalize();</span></span><span class="line"><span class="cl"> // Validate that the resolved path starts with the base directory</span></span><span class="line"><span class="cl"> if (!resolvedPath.startsWith(basePath)) {</span></span><span class="line"><span class="cl"> logSuspiciousActivity(userInput);</span></span><span class="line"><span class="cl"> throw new SecurityException("Invalid file path: path traversal attempt detected.");</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> return resolvedPath;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Logs suspicious activity for further analysis.</span></span><span class="line"><span class="cl"> *</span></span><span class="line"><span class="cl"> * @param userInput The suspicious user input.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> private static void logSuspiciousActivity(String userInput) {</span></span><span class="line"><span class="cl"> logger.warning("Suspicious file access attempt: " + userInput);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> /**</span></span><span class="line"><span class="cl"> * Example usage of the secure file path validation.</span></span><span class="line"><span class="cl"> *</span></span><span class="line"><span class="cl"> * @param args Command line arguments.</span></span><span class="line"><span class="cl"> */</span></span><span class="line"><span class="cl"> public static void main(String[] args) {</span></span><span class="line"><span class="cl"> String baseDir = "/var/www/uploads";</span></span><span class="line"><span class="cl"> String userInput = "example.txt";</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> Path filePath = getSecureFilePath(baseDir, userInput);</span></span><span class="line"><span class="cl"> System.out.println("Validated file path: " + filePath);</span></span><span class="line"><span class="cl"> } catch (SecurityException | IllegalArgumentException | IOException e) {</span></span><span class="line"><span class="cl"> e.printStackTrace();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><h3 id="implement-logging-and-monitoring">Implement Logging and Monitoring</h3><p>Logging and monitoring are essential to effectively prevent or deal with CWE-22 (Path Traversal) vulnerabilities. Here&rsquo;s why these practices are critical:</p><h4 id="detecting-suspicious-activity">Detecting Suspicious Activity</h4><p>Logging and monitoring allow you to detect suspicious activities that might indicate a path traversal attempt. By capturing and analysing logs, you can identify unusual patterns or attempts to access files and directories that should be off-limits.</p><p><strong>Example</strong> : Logging all file access attempts, including the requested paths, can help you spot anomalies such as attempts to use<code>../</code> sequences to access parent directories.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Logger</span><span class="w"/><span class="n">logger</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Logger</span><span class="p">.</span><span class="na">getLogger</span><span class="p">(</span><span class="n">SecureFileHandler</span><span class="p">.</span><span class="na">class</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">logFileAccessAttempt</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">filePath</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">.</span><span class="na">info</span><span class="p">(</span><span class="s">"File access attempt: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">filePath</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h4 id="incident-response-and-forensics">Incident Response and Forensics</h4><p>In a security incident, having detailed logs can help in forensic analysis to understand how the attack was carried out. This information is vital for closing security gaps and preventing future attacks.</p><p><strong>Example</strong> : Detailed logs can show the sequence of operations leading up to a detected breach, including the exact user inputs and system responses.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">filePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">resolveFilePath</span><span class="p">(</span><span class="n">userInput</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logFileAccessAttempt</span><span class="p">(</span><span class="n">filePath</span><span class="p">.</span><span class="na">toString</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">SecurityException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="p">.</span><span class="na">warning</span><span class="p">(</span><span class="s">"Security exception: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">getMessage</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="n">e</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h4 id="accountability-and-compliance">Accountability and Compliance</h4><p>Many regulatory frameworks and standards require logging and monitoring as part of their compliance requirements. Implementing these practices ensures that you meet legal and regulatory obligations.</p><p><strong>Example</strong> : Compliance with standards such as PCI-DSS or GDPR often requires comprehensive logging of security-related events to ensure accountability.</p><h4 id="proactive-threat-detection">Proactive Threat Detection</h4><p>Continuous monitoring allows you to detect and respond to threats proactively before they cause significant damage. Real-time monitoring solutions can alert you to potential path traversal attacks as they occur.</p><p><strong>Example</strong> : Implementing real-time alerts for suspicious file access patterns can help take immediate corrective actions.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// Example of setting up a real-time alert system (pseudo-code)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">detectedPathTraversalAttempt</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">alertSecurityTeam</span><span class="p">(</span><span class="s">"Potential path traversal attack detected: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">attemptedPath</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h4 id="improving-security-posture">Improving Security Posture</h4><p>Regularly analysing logs and monitoring data helps improve overall security posture by identifying weaknesses and refining security policies.</p><p><strong>Example</strong> : Reviewing access logs can highlight frequent user errors or misconfigurations that could be exploited, allowing you to address these proactively.</p><h4 id="real-time-monitoring">Real-Time Monitoring</h4><p>Use monitoring tools and services to keep track of log data and generate alerts for suspicious activities. Tools like ELK Stack (Elasticsearch, Logstash, Kibana), Splunk, or cloud-based solutions such as AWS CloudWatch and Azure Monitor can be useful.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// Pseudo-code for setting up real-time monitoring and alerts</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">detectPathTraversal</span><span class="p">(</span><span class="n">logFileAccessAttempt</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">triggerAlert</span><span class="p">(</span><span class="s">"Potential path traversal attack detected: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">logFileAccessAttempt</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Implementing logging and monitoring is not just a best practice but a necessary component of a robust security strategy. It helps in the early detection of threats, compliance with regulatory standards, effective incident response, and overall security posture improvement. Ensuring that all file access attempts and related activities are logged and monitored can better protect your applications from CWE-22 and other security vulnerabilities.</p><h3 id="use-securerandom-for-temporary-files">Use SecureRandom for Temporary Files</h3><p>Using<code>SecureRandom</code> to create temporary files in Java ensures better security and randomness, reducing the risk of vulnerabilities such as CWE-22. Here are best practices for using<code>SecureRandom</code> for this purpose:</p><p><strong>Instantiate SecureRandom</strong> : Create an instance of<code>SecureRandom</code> to generate random values for temporary file names or other purposes requiring secure randomness.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="o">**</span><span class="n">SecureRandom</span><span class="w"/><span class="n">secureRandom</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecureRandom</span><span class="p">();</span><span class="o">**</span></span></span></code></pre></div></div><p><strong>Generate Secure Random Values</strong> : Use<code>SecureRandom</code> to generate a sequence of bytes that can be converted into a string or used directly in file names.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">randomBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">16</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">secureRandom</span><span class="p">.</span><span class="na">nextBytes</span><span class="p">(</span><span class="n">randomBytes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">randomString</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">BigInteger</span><span class="p">(</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">randomBytes</span><span class="p">).</span><span class="na">toString</span><span class="p">(</span><span class="n">16</span><span class="p">);</span></span></span></code></pre></div></div><p><strong>Create Temporary Files with Secure Names</strong> : Create temporary files using the random string generated by<code>SecureRandom</code>. This helps prevent predictable file names, reducing the risk of collisions or attacks.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Path</span><span class="w"/><span class="n">tempDir</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="na">getProperty</span><span class="p">(</span><span class="s">"java.io.tmpdir"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">tempDir</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="s">"tempFile_"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">randomString</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">".tmp"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">createFile</span><span class="p">(</span><span class="n">tempFile</span><span class="p">);</span></span></span></code></pre></div></div><p><strong>Ensure File Permissions</strong> : Set appropriate file permissions to prevent unauthorized users from accessing the temporary files.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="o">**</span><span class="n">Files</span><span class="p">.</span><span class="na">setPosixFilePermissions</span><span class="p">(</span><span class="n">tempFile</span><span class="p">,</span><span class="w"/><span class="n">PosixFilePermissions</span><span class="p">.</span><span class="na">fromString</span><span class="p">(</span><span class="s">"rw-------"</span><span class="p">));</span><span class="o">**</span></span></span></code></pre></div></div><p><strong>Handle SecureRandom Properly</strong> : Ensure that<code>SecureRandom</code> is not reseeded unnecessarily, as it can reduce the randomness quality. Initialise it once and reuse the instance.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">SecureRandom</span><span class="w"/><span class="n">secureRandom</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecureRandom</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="o">//</span><span class="w"/><span class="n">Use</span><span class="w"/><span class="n">secureRandom</span><span class="w"/><span class="n">throughout</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">application</span></span></span></code></pre></div></div><p><strong>Use<code>java.nio.file</code> for File Operations</strong> : Utilize the NIO package to benefit from its security features and avoid common pitfalls associated with older IO methods.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="o">**</span><span class="n">Path</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">createTempFile</span><span class="p">(</span><span class="n">tempDir</span><span class="p">,</span><span class="w"/><span class="s">"tempFile_"</span><span class="p">,</span><span class="w"/><span class="s">".tmp"</span><span class="p">);</span><span class="o">**</span></span></span></code></pre></div></div><h3 id="use-filesystems-for-consistent-path-handling">Use FileSystems for Consistent Path Handling</h3><p>Using<code>FileSystems</code> in Java for consistent path handling ensures that paths are managed in a compatible way across different platforms and file systems. Here are some best practices and techniques for leveraging<code>FileSystems</code> in Java:</p><h4 id="key-concepts">Key Concepts</h4><p><strong>FileSystems Class</strong> :</p><p>The<code>java.nio.file.FileSystems</code> class provides factory methods for creating file system instances and obtaining default file systems.</p><p><strong>Path Interface</strong> :</p><p>The<code>java.nio.file.Path</code> interface represents a file or directory path in a platform-independent way.</p><p><strong>Standardise Path Creation</strong> :</p><p>Use<code>FileSystems</code> to create paths consistently.</p><h4 id="best-practices">Best Practices</h4><p><strong>Obtain the Default FileSystem</strong></p><p>The default file system corresponds to the platform&rsquo;s file system on which the Java virtual machine runs.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.FileSystem</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.FileSystems</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">FileSystem</span><span class="w"/><span class="n">defaultFileSystem</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">FileSystems</span><span class="p">.</span><span class="na">getDefault</span><span class="p">();</span></span></span></code></pre></div></div><p><strong>Create Paths Using the FileSystem</strong></p><p>Create<code>Path</code> objects using the<code>FileSystem</code> to ensure paths are created consistently.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Path</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Path</span><span class="w"/><span class="n">path</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">defaultFileSystem</span><span class="p">.</span><span class="na">getPath</span><span class="p">(</span><span class="s">"/var/www/uploads"</span><span class="p">,</span><span class="w"/><span class="s">"example.txt"</span><span class="p">);</span></span></span></code></pre></div></div><p><strong>Handle Paths Consistently Across Platforms</strong></p><p>Using<code>FileSystems</code> helps create paths compatible with different operating systems.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Path</span><span class="w"/><span class="n">windowsPath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">defaultFileSystem</span><span class="p">.</span><span class="na">getPath</span><span class="p">(</span><span class="s">"C:\\Users\\Public\\Documents"</span><span class="p">,</span><span class="w"/><span class="s">"example.txt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Path</span><span class="w"/><span class="n">unixPath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">defaultFileSystem</span><span class="p">.</span><span class="na">getPath</span><span class="p">(</span><span class="s">"/home/user/docs"</span><span class="p">,</span><span class="w"/><span class="s">"example.txt"</span><span class="p">);</span></span></span></code></pre></div></div><p><strong>Use Paths for Safe File Operations</strong></p><p>Using<code>Paths</code> from<code>FileSystems</code> ensures that file operations are performed safely and consistently.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Files</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">Files</span><span class="p">.</span><span class="na">exists</span><span class="p">(</span><span class="n">path</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">createFile</span><span class="p">(</span><span class="n">path</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Perform file operations</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Normalise and Resolve Paths</strong></p><p>Always normalise and resolve paths to avoid relative and symlinks issues.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Path</span><span class="w"/><span class="n">basePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">defaultFileSystem</span><span class="p">.</span><span class="na">getPath</span><span class="p">(</span><span class="s">"/var/www/uploads"</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Path</span><span class="w"/><span class="n">userPath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">basePath</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="s">"example.txt"</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">userPath</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="n">basePath</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecurityException</span><span class="p">(</span><span class="s">"Path traversal attempt detected"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h4 id="example-implementation-1">Example Implementation</h4><p>Here is an example demonstrating the use of<code>FileSystems</code> for consistent path handling:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.FileSystem</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.FileSystems</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Path</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Files</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.attribute.PosixFilePermissions</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SecurePathHandler</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">FileSystem</span><span class="w"/><span class="n">fileSystem</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">FileSystems</span><span class="p">.</span><span class="na">getDefault</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Path</span><span class="w"/><span class="nf">createSecureTempFile</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">prefix</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">suffix</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">tempDir</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fileSystem</span><span class="p">.</span><span class="na">getPath</span><span class="p">(</span><span class="n">System</span><span class="p">.</span><span class="na">getProperty</span><span class="p">(</span><span class="s">"java.io.tmpdir"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">createTempFile</span><span class="p">(</span><span class="n">tempDir</span><span class="p">,</span><span class="w"/><span class="n">prefix</span><span class="p">,</span><span class="w"/><span class="n">suffix</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Files</span><span class="p">.</span><span class="na">setPosixFilePermissions</span><span class="p">(</span><span class="n">tempFile</span><span class="p">,</span><span class="w"/><span class="n">PosixFilePermissions</span><span class="p">.</span><span class="na">fromString</span><span class="p">(</span><span class="s">"rw-------"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">tempFile</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Path</span><span class="w"/><span class="nf">resolveAndValidatePath</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">baseDir</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">userInput</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">SecurityException</span><span class="p">,</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">basePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fileSystem</span><span class="p">.</span><span class="na">getPath</span><span class="p">(</span><span class="n">baseDir</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">resolvedPath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">basePath</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">userInput</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">resolvedPath</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="n">basePath</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecurityException</span><span class="p">(</span><span class="s">"Invalid file path: path traversal attempt detected."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">resolvedPath</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">tempFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">createSecureTempFile</span><span class="p">(</span><span class="s">"tempFile_"</span><span class="p">,</span><span class="w"/><span class="s">".tmp"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Temporary file created: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">tempFile</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">baseDir</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"/var/www/uploads"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">userInput</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"example.txt"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">filePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">resolveAndValidatePath</span><span class="p">(</span><span class="n">baseDir</span><span class="p">,</span><span class="w"/><span class="n">userInput</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Validated file path: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">filePath</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">SecurityException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Using<code>FileSystems</code> for consistent path handling in Java ensures that your application can handle paths in a platform-independent manner. This helps in creating secure, reliable, and maintainable file-handling code. Following these best practices can prevent common pitfalls such as path traversal vulnerabilities and ensure that file operations are performed safely and consistently.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><media:content url="https://svenruppert.com/images/2024/05/0FC55170-E47B-4CE9-AF74-DD1AD9B9EAAF.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/05/0FC55170-E47B-4CE9-AF74-DD1AD9B9EAAF.jpg"/><enclosure url="https://svenruppert.com/images/2024/05/0FC55170-E47B-4CE9-AF74-DD1AD9B9EAAF.jpg" type="image/jpeg" length="0"/></item><item><title>CWE-22: Improper Limitation of a Pathname to a Restricted Directory</title><link>https://svenruppert.com/posts/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/</link><pubDate>Tue, 21 May 2024 14:33:53 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/</guid><description>CWE-22, commonly called &ldquo;Path Traversal,&rdquo; is a vulnerability when an application fails to appropriately limit the paths users can access through a user-provided input. This can allow attackers to access directories and files outside the intended directory, leading to unauthorised access and potential system compromise. This vulnerability is particularly significant in Java applications due to the ubiquitous use of file handling and web resources. This document will delve into the nature of CWE-22, its implications, exploitation methods, and, most importantly, strategies to mitigate such vulnerabilities in Java applications.</description><content:encoded>&lt;![CDATA[<p>CWE-22, commonly called &ldquo;Path Traversal,&rdquo; is a vulnerability when an application fails to appropriately limit the paths users can access through a user-provided input. This can allow attackers to access directories and files outside the intended directory, leading to unauthorised access and potential system compromise. This vulnerability is particularly significant in Java applications due to the ubiquitous use of file handling and web resources. This document will delve into the nature of CWE-22, its implications, exploitation methods, and, most importantly, strategies to mitigate such vulnerabilities in Java applications.</p><ol><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#understanding-cwe-22">Understanding CWE-22</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#implications-of-cwe-22">Implications of CWE-22</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#exploitation-techniques">Exploitation Techniques</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#mitigation-strategies-in-java">Mitigation Strategies in Java</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#case-study-a-vulnerable-java-application">Case Study: A Vulnerable Java Application</a><ol><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#vulnerable-code">Vulnerable Code</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#secure-code">Secure Code</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#what-java-libraries-are-helping-against-cwe-22">What Java Libraries are Helping against CWE-22</a><ol><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#apache-commons-io">Apache Commons IO</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#owasp-java-encoder">OWASP Java Encoder</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#apache-shiro">Apache Shiro</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#spring-security">Spring Security</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#tika">Tika</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#esapi-enterprise-security-api">ESAPI (Enterprise Security API)</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#java-nio-new-i-o">Java NIO (New I/O)</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#hibernate-validator">Hibernate Validator</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#what-cves-are-based-on-cwe-22">What CVEs are based on CWE-22</a><ol><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#cve-2020-9484">CVE-2020-9484</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#cve-2019-0232">CVE-2019-0232</a></li><li><a href="https://svenruppert.com/2024/05/21/cwe-22-improper-limitation-of-a-pathname-to-a-restricted-directory/#cve-2018-11784">CVE-2018-11784</a></li></ol></li></ol><h2 id="understanding-cwe-22">Understanding CWE-22</h2><p>CWE-22 is categorised under &ldquo;Improper Limitation of a Pathname to a Restricted Directory (&lsquo;Path Traversal&rsquo;).&rdquo; This occurs when an application constructs a path using user input without proper validation or sanitisation, allowing the user to specify paths that traverse outside the intended directory. Sequences such as<code>../</code> can be used to move up the directory structure.</p><p>For example, consider the following Java code snippet that reads a file based on user input:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="s">"file"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">File</span><span class="w"/><span class="n">file</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"/var/www/uploads/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">FileInputStream</span><span class="w"/><span class="n">fis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="n">file</span><span class="p">);</span></span></span></code></pre></div></div><p>If the<code>fileName</code> parameter is not properly validated, an attacker can manipulate it to access files outside the<code>/var/www/uploads/</code> directory, such as<code>/etc/passwd</code>, by using a path traversal sequence (<code>../../etc/passwd</code>).</p><h2 id="implications-of-cwe-22">Implications of CWE-22</h2><p>The implications of CWE-22 can be severe, ranging from unauthorised access to sensitive files to full system compromise. Some of the potential impacts include:</p><p><strong>Exposure of Sensitive Information</strong> : Attackers can access sensitive files such as configuration files, passwords, and personal data.</p><p><strong>Data Integrity Compromise</strong> : Attackers can modify files, potentially leading to data corruption or alteration of critical application files.</p><p><strong>Denial of Service (DoS)</strong> : Attackers can disrupt normal operations by accessing and modifying system files.</p><p><strong>Execution of Arbitrary Code</strong> : In extreme cases, attackers might execute arbitrary code if they gain access to executable files or scripts.</p><h2 id="exploitation-techniques">Exploitation Techniques</h2><p>To exploit a path traversal vulnerability, an attacker typically uses special characters and patterns in the input to navigate the file system. Here are some standard techniques:</p><p><strong>Dot-Dot-Slash (<code>../</code>)</strong> : The most common method where the attacker uses<code>../</code> to move up the directory structure.</p><p><strong>Encoded Characters</strong> : Attackers may use URL encoding (<code>%2e%2e%2f</code>) or other encoding schemes to bypass simple input filters.</p><p><strong>Null Byte Injection</strong> : Sometimes, null bytes (<code>%00</code>) are used to terminate strings early, effectively ignoring any appended extensions or path components.</p><h2 id="mitigation-strategies-in-java">Mitigation Strategies in Java</h2><p>Mitigating CWE-22 in Java involves a combination of secure coding practices, input validation, and proper use of APIs. Here are some detailed strategies:</p><p><strong>Canonicalisation and Normalization</strong> :</p><p>Ensure that the file paths are normalised before use. Java provides methods to normalise paths, which can help mitigate traversal attacks.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Paths</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Path</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">File</span><span class="w"/><span class="nf">getFile</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">basePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"/var/www/uploads/"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">filePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">basePath</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">fileName</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">filePath</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="n">basePath</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecurityException</span><span class="p">(</span><span class="s">"Attempted path traversal attack detected"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">filePath</span><span class="p">.</span><span class="na">toFile</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Input Validation and Sanitization</strong> :</p><p>Perform strict validation on user inputs. Reject any input that contains potentially malicious patterns such as<code>../</code>, URL-encoded sequences, or other traversal patterns.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">sanitizeFileName</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">fileName</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">fileName</span><span class="p">.</span><span class="na">contains</span><span class="p">(</span><span class="s">".."</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">||</span><span class="w"/><span class="n">fileName</span><span class="p">.</span><span class="na">contains</span><span class="p">(</span><span class="s">"/"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">||</span><span class="w"/><span class="n">fileName</span><span class="p">.</span><span class="na">contains</span><span class="p">(</span><span class="s">"\\"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Invalid file name"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">fileName</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Whitelist Approach</strong> :</p><p>Use a whitelist approach to validate filenames against a set of allowed values or patterns. This can be more effective than blacklisting known destructive patterns.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isValidFileName</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">fileName</span><span class="p">.</span><span class="na">matches</span><span class="p">(</span><span class="s">"[a-zA-Z0-9._-]+"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>File Access Controls</strong> :</p><p>Restrict file permissions on the server to limit access only to necessary files and directories. This reduces the impact of potential exploits.</p><p><strong>Logging and Monitoring</strong> :</p><p>Implement comprehensive logging and monitoring to detect and respond to suspicious activities. Logs should capture sufficient details to trace potential exploitation attempts.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.logging.Logger</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">FileAccessLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Logger</span><span class="w"/><span class="n">LOGGER</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Logger</span><span class="p">.</span><span class="na">getLogger</span><span class="p">(</span><span class="n">FileAccessLogger</span><span class="p">.</span><span class="na">class</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">logAccessAttempt</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">LOGGER</span><span class="p">.</span><span class="na">warning</span><span class="p">(</span><span class="s">"Attempted access to file: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h2 id="case-study-a-vulnerable-java-application">Case Study: A Vulnerable Java Application</h2><p>Let’s consider a hypothetical case study to illustrate the application of these mitigation strategies. Suppose we have a simple Java web application that allows users to download files from the server.</p><h3 id="vulnerable-code">Vulnerable Code</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@WebServlet</span><span class="p">(</span><span class="s">"/download"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">FileDownloadServlet</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">HttpServlet</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">protected</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">doGet</span><span class="p">(</span><span class="n">HttpServletRequest</span><span class="w"/><span class="n">request</span><span class="p">,</span><span class="w"/><span class="n">HttpServletResponse</span><span class="w"/><span class="n">response</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">ServletException</span><span class="p">,</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="s">"file"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">File</span><span class="w"/><span class="n">file</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"/var/www/uploads/"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">file</span><span class="p">.</span><span class="na">exists</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">FileInputStream</span><span class="w"/><span class="n">fis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="n">file</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">setContentType</span><span class="p">(</span><span class="s">"application/octet-stream"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Content-Disposition"</span><span class="p">,</span><span class="w"/><span class="s">"attachment; filename=\""</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">file</span><span class="p">.</span><span class="na">getName</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"\""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">4096</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">bytesRead</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">while</span><span class="w"/><span class="p">((</span><span class="n">bytesRead</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fis</span><span class="p">.</span><span class="na">read</span><span class="p">(</span><span class="n">buffer</span><span class="p">))</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="o">-</span><span class="n">1</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">getOutputStream</span><span class="p">().</span><span class="na">write</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">bytesRead</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fis</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">sendError</span><span class="p">(</span><span class="n">HttpServletResponse</span><span class="p">.</span><span class="na">SC_NOT_FOUND</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This servlet reads the<code>file</code> parameter from the request and constructs a<code>File</code> object. Without proper validation, an attacker can exploit this to download arbitrary files from the server.</p><h3 id="secure-code">Secure Code</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@WebServlet</span><span class="p">(</span><span class="s">"/download"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">FileDownloadServlet</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">HttpServlet</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">protected</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">doGet</span><span class="p">(</span><span class="n">HttpServletRequest</span><span class="w"/><span class="n">request</span><span class="p">,</span><span class="w"/><span class="n">HttpServletResponse</span><span class="w"/><span class="n">response</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">ServletException</span><span class="p">,</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">sanitizeFileName</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="s">"file"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">basePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"/var/www/uploads/"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">filePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">basePath</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">fileName</span><span class="p">).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">filePath</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="n">basePath</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">sendError</span><span class="p">(</span><span class="n">HttpServletResponse</span><span class="p">.</span><span class="na">SC_FORBIDDEN</span><span class="p">,</span><span class="w"/><span class="s">"Invalid file path"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">File</span><span class="w"/><span class="n">file</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">filePath</span><span class="p">.</span><span class="na">toFile</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">file</span><span class="p">.</span><span class="na">exists</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">FileInputStream</span><span class="w"/><span class="n">fis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="n">file</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">setContentType</span><span class="p">(</span><span class="s">"application/octet-stream"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">setHeader</span><span class="p">(</span><span class="s">"Content-Disposition"</span><span class="p">,</span><span class="w"/><span class="s">"attachment; filename=\""</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">file</span><span class="p">.</span><span class="na">getName</span><span class="p">()</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"\""</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">4096</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">bytesRead</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">while</span><span class="w"/><span class="p">((</span><span class="n">bytesRead</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fis</span><span class="p">.</span><span class="na">read</span><span class="p">(</span><span class="n">buffer</span><span class="p">))</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="o">-</span><span class="n">1</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">getOutputStream</span><span class="p">().</span><span class="na">write</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">bytesRead</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fis</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">sendError</span><span class="p">(</span><span class="n">HttpServletResponse</span><span class="p">.</span><span class="na">SC_NOT_FOUND</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">sanitizeFileName</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">fileName</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">fileName</span><span class="p">.</span><span class="na">contains</span><span class="p">(</span><span class="s">".."</span><span class="p">)</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">fileName</span><span class="p">.</span><span class="na">contains</span><span class="p">(</span><span class="s">"/"</span><span class="p">)</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">fileName</span><span class="p">.</span><span class="na">contains</span><span class="p">(</span><span class="s">"\\"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Invalid file name"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">fileName</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In the secure version, the<code>sanitizeFileName</code> method ensures the file name is free from traversal sequences, and the path is normalised and checked to prevent directory traversal.</p><p>CWE-22, or path traversal, is a critical vulnerability that can have severe consequences if not adequately mitigated. In Java applications, it is essential to employ a combination of secure coding practices, input validation, canonicalisation, and access controls to safeguard against this threat. By understanding the nature of CWE-22 and implementing robust security measures, developers can significantly reduce the risk of unauthorised file access and protect their applications from potential exploitation.</p><h2 id="what-java-libraries-are-helping-against-cwe-22">What Java Libraries are Helping against CWE-22</h2><p>Several Java libraries and tools can help developers prevent CWE-22 (Path Traversal) vulnerabilities by providing robust input validation, path normalisation, and security mechanisms. Here are some of the most notable ones:</p><h3 id="apache-commons-io">Apache Commons IO</h3><p>Apache Commons IO provides a variety of utilities for working with the file system, which can help prevent path traversal vulnerabilities.</p><p><strong>FilenameUtils</strong> : This class contains methods for normalising and validating file paths.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.apache.commons.io.FilenameUtils</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">basePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"/var/www/uploads/"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">FilenameUtils</span><span class="p">.</span><span class="na">normalize</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="s">"file"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">FilenameUtils</span><span class="p">.</span><span class="na">directoryContains</span><span class="p">(</span><span class="n">basePath</span><span class="p">,</span><span class="w"/><span class="n">basePath</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecurityException</span><span class="p">(</span><span class="s">"Attempted path traversal attack detected"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h3 id="owasp-java-encoder">OWASP Java Encoder</h3><p>The OWASP Java Encoder library encodes untrusted input to help protect against injection attacks, including those involving path traversal.</p><p><strong>Encoder</strong> : Provides methods to encode user input for various contexts, including filenames, safely.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.owasp.encoder.Encode</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">safeFileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Encode</span><span class="p">.</span><span class="na">forJava</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="s">"file"</span><span class="p">));</span></span></span></code></pre></div></div><h3 id="apache-shiro">Apache Shiro</h3><p>Apache Shiro is a powerful security framework that provides robust access control mechanisms for enforcing file access policies.</p><p><strong>Path Permissions</strong> : Shiro allows you to define fine-grained access control policies related to file access.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="c1">// Define file access permissions in Shiro configuration</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">[</span><span class="n">urls</span><span class="o">]</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">/</span><span class="n">var</span><span class="o">/</span><span class="n">www</span><span class="o">/</span><span class="n">uploads</span><span class="o">/**</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">authc</span><span class="p">,</span><span class="w"/><span class="n">perms</span><span class="o">[</span><span class="s">"file:read"</span><span class="o">]</span></span></span></code></pre></div></div><h3 id="spring-security">Spring Security</h3><p>Spring Security is a comprehensive security framework that integrates seamlessly with Spring applications, providing mechanisms for authentication and authorisation.</p><p><strong>Access Control</strong> : Spring Security can be configured to enforce strict access controls on file resources.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="nd">@PreAuthorize</span><span class="p">(</span><span class="s">"hasPermission(#filePath, 'read')"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">readFile</span><span class="p">(</span><span class="n">Path</span><span class="w"/><span class="n">filePath</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// read file logic</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h3 id="tika">Tika</h3><p>Apache Tika is a library for detecting and extracting metadata and content from various file types. It includes utilities for working with file paths safely.</p><p><strong>Tika IOUtils</strong> : Utility methods for safe file operations.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.apache.tika.io.IOUtils</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">safeFileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">IOUtils</span><span class="p">.</span><span class="na">toString</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="s">"file"</span><span class="p">),</span><span class="w"/><span class="n">StandardCharsets</span><span class="p">.</span><span class="na">UTF_8</span><span class="p">);</span></span></span></code></pre></div></div><h3 id="esapi-enterprise-security-api">ESAPI (Enterprise Security API)</h3><p>The OWASP ESAPI library provides a comprehensive set of security controls, including file-handling utilities that can help prevent path traversal attacks.</p><p><strong>Validator</strong> : Use ESAPI&rsquo;s Validator to validate filenames.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.owasp.esapi.ESAPI</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.owasp.esapi.Validator</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Validator</span><span class="w"/><span class="n">validator</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ESAPI</span><span class="p">.</span><span class="na">validator</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">String</span><span class="w"/><span class="n">safeFileName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">validator</span><span class="p">.</span><span class="na">getValidInput</span><span class="p">(</span><span class="s">"file name"</span><span class="p">,</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="s">"file"</span><span class="p">),</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"Filename"</span><span class="p">,</span><span class="w"/><span class="n">255</span><span class="p">,</span><span class="w"/><span class="kc">false</span><span class="p">);</span></span></span></code></pre></div></div><h3 id="java-nio-new-io">Java NIO (New I/O)</h3><p>Java NIO provides modern APIs for file operations, including path manipulation and validation.</p><p><strong>Path and Files</strong> : Use<code>java.nio.file.Path</code> and<code>java.nio.file.Files</code> for secure file operations.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Path</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Paths</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kn">import</span><span class="w"/><span class="nn">java.nio.file.Files</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">basePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Paths</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="s">"/var/www/uploads/"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Path</span><span class="w"/><span class="n">filePath</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">basePath</span><span class="p">.</span><span class="na">resolve</span><span class="p">(</span><span class="n">request</span><span class="p">.</span><span class="na">getParameter</span><span class="p">(</span><span class="s">"file"</span><span class="p">)).</span><span class="na">normalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">filePath</span><span class="p">.</span><span class="na">startsWith</span><span class="p">(</span><span class="n">basePath</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">SecurityException</span><span class="p">(</span><span class="s">"Attempted path traversal attack detected"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">Files</span><span class="p">.</span><span class="na">exists</span><span class="p">(</span><span class="n">filePath</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// read file logic</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h3 id="hibernate-validator">Hibernate Validator</h3><p>Hibernate Validator, the reference implementation of the Bean Validation API, can be used to enforce validation constraints on user inputs.</p><p><strong>Custom Constraints</strong> : Define custom validation constraints for filenames.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">javax.validation.constraints.Pattern</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">FileRequest</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Pattern</span><span class="p">(</span><span class="n">regexp</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"[a-zA-Z0-9._-]+"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// getters and setters</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>The Java libraries and tools provide robust mechanisms to prevent CWE-22 (Path Traversal) vulnerabilities. Using these libraries, developers can ensure that user inputs are appropriately validated, paths are normalised, and access controls are strictly enforced. This multi-layered approach significantly reduces the risk of unauthorised file access and enhances the overall security of Java applications.</p><h2 id="what-cves-are-based-on-cwe-22">What CVEs are based on CWE-22</h2><p>Several Common Vulnerabilities and Exposures (CVEs) related to Java applications have been reported that are based on CWE-22, the Improper Limitation of a Pathname to a Restricted Directory (&lsquo;Path Traversal&rsquo;). These CVEs highlight path traversal vulnerabilities&rsquo; real-world implications and impact in various Java-based systems and libraries. Here are some notable examples:</p><h3 id="cve-2020-9484">CVE-2020-9484</h3><p><strong>Description</strong> : Apache Tomcat HTTP/2 Request Smuggling and Path Traversal.</p><p><strong>Affected Versions</strong> : Apache Tomcat 9.0.0.M1 to 9.0.35, 8.5.0 to 8.5.55, and 7.0.0 to 7.0.104.</p><p><strong>Details</strong> : This vulnerability allowed an attacker to upload files to arbitrary locations via a specially crafted request. The issue was related to the improper handling of user input in file upload paths, leading to a path traversal vulnerability.</p><p><strong>Mitigation</strong> : Upgrade to the latest versions of Apache Tomcat that include the patch for this vulnerability.</p><h3 id="cve-2019-0232">CVE-2019-0232</h3><p><strong>Description</strong> : Apache Tomcat Remote Code Execution via CGI Servlet.</p><p><strong>Affected Versions</strong> : Apache Tomcat 9.0.0.M1 to 9.0.17, 8.5.0 to 8.5.39, and 7.0.0 to 7.0.93.</p><p><strong>Details</strong> : This CVE was related to a path traversal vulnerability that allowed attackers to achieve remote code execution by manipulating the CGI Servlet configuration.</p><p><strong>Mitigation</strong> : Disable the CGI Servlet if it is unnecessary or upgrade to a version that fixes the vulnerability.</p><h3 id="cve-2018-11784">CVE-2018-11784</h3><p><strong>Description</strong> : Apache JMeter Path Traversal Vulnerability.</p><p><strong>Affected Versions</strong> : Apache JMeter 3.0 to 4.0.</p><p><strong>Details</strong> : This vulnerability allowed attackers to access files outside the intended directories via a path traversal attack, leveraging improper file path validation in the application.</p><p><strong>Mitigation</strong> : Upgrade to Apache JMeter 5.0 or later versions where the issue has been resolved.</p><p>These CVEs highlight the importance of addressing CWE-22 vulnerabilities in Java applications. Regularly updating libraries, frameworks, and application code is crucial to mitigating such vulnerabilities. Developers should adopt best practices for input validation, path normalisation, and robust security configurations to protect against path traversal attacks.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><media:content url="https://svenruppert.com/images/2024/05/76E8D5CE-C004-48CF-9CB9-D48A128A1074.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/05/76E8D5CE-C004-48CF-9CB9-D48A128A1074.jpg"/><enclosure url="https://svenruppert.com/images/2024/05/76E8D5CE-C004-48CF-9CB9-D48A128A1074.jpg" type="image/jpeg" length="0"/></item><item><title>CWE-416: Use After Free Vulnerabilities in Java</title><link>https://svenruppert.com/posts/cwe-416-use-after-free-vulnerabilities-in-java/</link><pubDate>Fri, 17 May 2024 12:17:30 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/cwe-416-use-after-free-vulnerabilities-in-java/</guid><description>CWE-416: Use After Free Use After Free (UAF) is a vulnerability that occurs when a program continues to use a pointer after it has been freed. This can lead to undefined behaviour, including crashes, data corruption, and security vulnerabilities. The problem arises because the memory referenced by the pointer may be reallocated for other purposes, potentially allowing attackers to exploit the situation.</description><content:encoded>&lt;![CDATA[<h2 id="cwe-416-use-after-free">CWE-416: Use After Free</h2><p>Use After Free (UAF) is a vulnerability that occurs when a program continues to use a pointer after it has been freed. This can lead to undefined behaviour, including crashes, data corruption, and security vulnerabilities. The problem arises because the memory referenced by the pointer may be reallocated for other purposes, potentially allowing attackers to exploit the situation.</p><ol><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#cwe-416-use-after-free">CWE-416: Use After Free</a><ol><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#causes">Causes:</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#consequences">Consequences:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#cwe-416-in-java">CWE-416 in Java</a><ol><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#scenario-1-mismanagement-of-external-resources">Scenario 1: Mismanagement of External Resources</a><ol><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#automatic-resource-management-with-try-with-resources">Automatic Resource Management with Try-With-Resources:</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#explicit-nullification">Explicit Nullification:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#scenario-2-inappropriate-use-of-weak-references">Scenario 2: Inappropriate Use of Weak References</a><ol><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#strong-references-for-critical-data">Strong References for Critical Data:</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#check-references-before-use">Check References Before Use:</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#what-cve-s-are-based-on-cwe-416-in-java">What CVE´s are based on CWE-416 in Java</a><ol><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#cve-2022-45146">CVE-2022-45146:</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#cve-2023-38703">CVE-2023-38703:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#what-design-patterns-are-used-to-avoid-cwe-416-in-java">What Design Patterns are used to avoid CWE-416 in Java?</a><ol><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#raii-resource-acquisition-is-initialization-pattern">RAII (Resource Acquisition Is Initialization) Pattern</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#factory-pattern">Factory Pattern</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#singleton-pattern">Singleton Pattern</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#observer-pattern">Observer Pattern</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#decorator-pattern">Decorator Pattern</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#raii-resource-acquisition-is-initialization-pattern-in-java-without-using-try-with-resources">RAII (Resource Acquisition Is Initialization) Pattern in Java without using try-with-resources</a><ol><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#example-raii-implementation-without-try-with-resources">Example: RAII Implementation without try-with-resources</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#important-considerations">Important Considerations:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#cleaner-api-example">Cleaner API Example:</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#more-details-about-java-lang-ref-cleaner-please">More details about java.lang.ref.Cleaner, please</a><ol><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#how-to-use-java-lang-ref-cleaner">How to Use<code>java.lang.ref.Cleaner</code>:</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#example">Example:</a></li><li><a href="https://svenruppert.com/2024/05/17/cwe-416-use-after-free-vulnerabilities-in-java/#benefits">Benefits:</a></li></ol></li></ol><h3 id="causes">Causes:</h3><p><strong>Incorrect memory management</strong> : Forgetting to nullify pointers after freeing memory.</p><p><strong>Dangling pointers</strong> : Retaining references to freed memory and accessing it later.</p><p><strong>Double freeing</strong> : Freeing memory more than once, causing subsequent operations on the exact memory location.</p><h3 id="consequences">Consequences:</h3><p><strong>Security vulnerabilities</strong> : Attackers can exploit UAF to execute arbitrary code, escalate privileges, or cause denial of service.</p><p><strong>Data corruption</strong> : UAF can lead to unexpected changes in data, causing program instability or incorrect behaviour.</p><p><strong>Program crashes</strong> : Accessing freed memory can cause segmentation faults or other runtime errors.</p><h2 id="cwe-416-in-java">CWE-416 in Java</h2><p>With its automatic garbage collection, Java inherently protects against many types of memory management issues, including Use After Free (UAF) vulnerabilities. However, UAF-like issues can still occur in Java if resources other than memory (like file handles or network connections) are mismanaged. Below are some scenarios in Java that resemble UAF and strategies to mitigate them.</p><h3 id="scenario-1-mismanagement-of-external-resources">Scenario 1: Mismanagement of External Resources</h3><p>In Java, even though memory is managed by the garbage collector, other resources like file handles or network sockets can be mismanaged, leading to use-after-close problems.</p><p><strong>Example</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">UseAfterFreeExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">FileInputStream</span><span class="w"/><span class="n">fis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="s">"example.txt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Use the file input stream...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">fis</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fis</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Attempt to use the file input stream after it has been closed</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fis</span><span class="p">.</span><span class="na">read</span><span class="p">();</span><span class="w"/><span class="c1">// This will throw an IOException</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Attempted to read from a closed stream."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, after closing the<code>FileInputStream</code>, attempting to read from it results in an<code>IOException</code>.</p><h4 id="automatic-resource-management-with-try-with-resources">Automatic Resource Management with Try-With-Resources:</h4><p>Use the try-with-resources statement introduced in Java 7, which ensures each resource is closed at the end of the statement.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">UseAfterFreeFixed</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">FileInputStream</span><span class="w"/><span class="n">fis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="s">"example.txt"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Use the file input stream...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// fis is automatically closed here</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Attempting to use fis here will cause a compilation error</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h4 id="explicit-nullification">Explicit Nullification:</h4><p>Set references to<code>null</code> after closing them to avoid accidental reuse.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kn">import</span><span class="w"/><span class="nn">java.io.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">UseAfterFreeWithNull</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">FileInputStream</span><span class="w"/><span class="n">fis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="s">"example.txt"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Use the file input stream...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">fis</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fis</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">fis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/><span class="c1">// Prevent reuse</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// fis is now null, so the following attempt to use it will fail at compile-time</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">fis</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">fis</span><span class="p">.</span><span class="na">read</span><span class="p">();</span><span class="w"/><span class="c1">// This will not compile since fis is null</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Attempted to read from a closed stream."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h3 id="scenario-2-inappropriate-use-of-weak-references">Scenario 2: Inappropriate Use of Weak References</h3><p>While not a direct UAF issue, misusing weak references can lead to problems where objects are accessed after being reclaimed by the garbage collector.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.lang.ref.WeakReference;</span></span><span class="line"><span class="cl">public class WeakReferenceExample {</span></span><span class="line"><span class="cl"> public static void main(String[] args) {</span></span><span class="line"><span class="cl"> Object obj = new Object();</span></span><span class="line"><span class="cl"> WeakReference<span class="nt">&lt;Object&gt;</span> weakRef = new WeakReference<span class="err">&lt;</span>&gt;(obj);</span></span><span class="line"><span class="cl"> obj = null; // Eligible for garbage collection</span></span><span class="line"><span class="cl"> System.gc(); // Suggest garbage collection</span></span><span class="line"><span class="cl"> // Attempt to use the object after it may have been collected</span></span><span class="line"><span class="cl"> if (weakRef.get() != null) {</span></span><span class="line"><span class="cl"> System.out.println("Object is still alive.");</span></span><span class="line"><span class="cl"> } else {</span></span><span class="line"><span class="cl"> System.out.println("Object has been garbage collected.");</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>In this example, after nullifying the strong reference and suggesting garbage collection, accessing the weak reference may result in null, indicating the object has been collected.</p><h4 id="strong-references-for-critical-data">Strong References for Critical Data:</h4><p>Avoid using weak references for critical objects that are still needed.</p><h4 id="check-references-before-use">Check References Before Use:</h4><p>Always check if a weak reference has been cleared before using it.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">weakRef</span><span class="p">.</span><span class="k">get</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="k">null</span><span class="p">)</span><span class="w"/><span class="err">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">//</span><span class="w"/><span class="n">Safe</span><span class="w"/><span class="k">to</span><span class="w"/><span class="n">use</span><span class="w"/><span class="n">the</span><span class="w"/><span class="k">object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">Object</span><span class="w"/><span class="n">obj</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">weakRef</span><span class="p">.</span><span class="k">get</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">//</span><span class="w"/><span class="n">Use</span><span class="w"/><span class="n">obj</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="err">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="err">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">//</span><span class="w"/><span class="n">Handle</span><span class="w"/><span class="n">the</span><span class="w"/><span class="k">case</span><span class="w"/><span class="k">where</span><span class="w"/><span class="n">the</span><span class="w"/><span class="k">object</span><span class="w"/><span class="n">has</span><span class="w"/><span class="n">been</span><span class="w"/><span class="n">collected</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">System</span><span class="p">.</span><span class="k">out</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="s2">"Object has been garbage collected."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="err">}</span></span></span></code></pre></div></div><p>While Java’s automatic memory management reduces the risk of traditional UAF vulnerabilities, developers must still be cautious with resource management and weak references to avoid similar issues. Modern Java features like try-with-resources and being mindful of object references can help maintain robust and error-free code.</p><h2 id="what-cves-are-based-on-cwe-416-in-java">What CVE´s are based on CWE-416 in Java</h2><p>Here are some notable CVEs related to CWE-416: Use After Free vulnerabilities in Java:</p><h3 id="cve-2022-45146">CVE-2022-45146:</h3><p>This vulnerability is found in the FIPS Java API of Bouncy Castle BC-FJA before version 1.0.2.4. Changes in the JVM garbage collector in Java 13 and later versions can trigger this issue, causing temporary keys used by the module to be zeroed out while still in use. This results in errors or potential information loss. FIPS compliance users are unaffected as it applies to Java 7, 8, and 11.</p><h3 id="cve-2023-38703">CVE-2023-38703:</h3><p>This CVE affects the PJSIP multimedia communication library, written in multiple languages, including Java. The issue arises when the synchronisation between higher-level SRTP media and lower-level transport, such as UDP, is not maintained, leading to a use-after-free vulnerability. This can result in unexpected application termination or memory corruption.</p><p>These examples highlight the importance of proper memory and resource management in Java applications, even though the language manages memory through garbage collection. For more information on these vulnerabilities, visit the National Vulnerability Database (NVD) or specific advisories on platforms like GitHub and mvnrepository.</p><h2 id="what-design-patterns-are-used-to-avoid-cwe-416-in-java">What Design Patterns are used to avoid CWE-416 in Java?</h2><p>Several design patterns and best practices can be employed to ensure robust and safe memory and resource management in Java to avoid CWE-416 (Use After Free). While Java&rsquo;s garbage collector reduces the risk of traditional UAF vulnerabilities, the following patterns and practices help avoid related issues with resources like file handles, sockets, or other external resources.</p><h3 id="raii-resource-acquisition-is-initialization-pattern">RAII (Resource Acquisition Is Initialization) Pattern</h3><p>RAII is a design pattern that ties resource management to object lifetime. Although RAII is more commonly associated with C++, Java can leverage a similar approach using try-with-resources.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">FileInputStream</span><span class="w"/><span class="n">fis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="s">"example.txt"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Use the file input stream...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="o">//</span><span class="w"/><span class="n">fis</span><span class="w"/><span class="n">is</span><span class="w"/><span class="n">automatically</span><span class="w"/><span class="n">closed</span><span class="w"/><span class="n">here</span></span></span></code></pre></div></div><p>The try-with-resources statement ensures that each resource is closed at the end of the statement, reducing the risk of resource leaks and use-after-close issues.</p><h3 id="factory-pattern">Factory Pattern</h3><p>The Factory Pattern encapsulates an object&rsquo;s creation logic, allowing for central management of resource creation and ensuring that resources are properly initialized and managed.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ConnectionFactory</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Connection</span><span class="w"/><span class="nf">createConnection</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Implement resource creation and management</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Connection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Usage</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Connection</span><span class="w"/><span class="n">connection</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ConnectionFactory</span><span class="p">.</span><span class="na">createConnection</span><span class="p">();</span></span></span></code></pre></div></div><p>This pattern centralises resource management, making it easier to enforce consistent resource-handling practices.</p><h3 id="singleton-pattern">Singleton Pattern</h3><p>The Singleton Pattern ensures that a class has only one instance and provides a global point of access to it. This is particularly useful for managing shared resources like database connections or thread pools.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">DatabaseConnection</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">DatabaseConnection</span><span class="w"/><span class="n">instance</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="nf">DatabaseConnection</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Initialize the connection</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">synchronized</span><span class="w"/><span class="n">DatabaseConnection</span><span class="w"/><span class="nf">getInstance</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">instance</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">instance</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DatabaseConnection</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">instance</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>By controlling a single instance, the Singleton Pattern helps manage the lifecycle and closure of shared resources properly.</p><h3 id="observer-pattern">Observer Pattern</h3><p>The Observer Pattern can notify interested parties about resource lifecycle events, such as the availability or closing of a resource. This ensures that all application parts know the resource state and can act accordingly.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public interface ResourceListener {</span></span><span class="line"><span class="cl"> void onResourceClosed();</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl">public class Resource {</span></span><span class="line"><span class="cl"> private List<span class="nt">&lt;ResourceListener&gt;</span> listeners = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> public void addListener(ResourceListener listener) {</span></span><span class="line"><span class="cl"> listeners.add(listener);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public void close() {</span></span><span class="line"><span class="cl"> // Close the resource</span></span><span class="line"><span class="cl"> notifyListeners();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> private void notifyListeners() {</span></span><span class="line"><span class="cl"> for (ResourceListener listener : listeners) {</span></span><span class="line"><span class="cl"> listener.onResourceClosed();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Observers are notified of resource state changes, allowing them to update their behaviour accordingly.</p><h3 id="decorator-pattern">Decorator Pattern</h3><p>The Decorator Pattern allows for dynamic functionality extensions, including resource management features like logging or additional cleanup actions when resources are closed.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ResourceDecorator</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">AutoCloseable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">AutoCloseable</span><span class="w"/><span class="n">resource</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">ResourceDecorator</span><span class="p">(</span><span class="n">AutoCloseable</span><span class="w"/><span class="n">resource</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">resource</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">resource</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">close</span><span class="p">()</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Additional cleanup actions</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">resource</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">// Usage</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">ResourceDecorator</span><span class="w"/><span class="n">decoratedResource</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ResourceDecorator</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="s">"example.txt"</span><span class="p">)))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Use the resource</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This pattern enhances the resource management behaviour without modifying the original resource classes.</p><p>Developers can use these patterns to ensure better resource management and mitigate the risk of use-after-free vulnerabilities in Java applications.</p><h2 id="raii-resource-acquisition-is-initialization-pattern-in-java-without-using-try-with-resources">RAII (Resource Acquisition Is Initialization) Pattern in Java without using try-with-resources</h2><p>RAII (Resource Acquisition Is Initialization) is a design pattern that binds the lifecycle of resources to the lifetime of objects, ensuring resources are adequately released when the object goes out of scope. While Java does not directly support RAII due to its garbage collection system and lack of deterministic destruction, you can still achieve similar behaviour without using try-with-resources by employing custom management classes and finalisers.</p><p>Here’s how you can implement RAII in Java using a custom resource management class and finalisers:</p><h3 id="example-raii-implementation-without-try-with-resources">Example: RAII Implementation without try-with-resources</h3><p><strong>Create a Resource Management Class</strong> :</p><p>This class will manage the resource and ensure it is adequately released when the object is no longer needed.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ManagedResource</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">isReleased</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">ManagedResource</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Acquire the resource</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Resource acquired"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">useResource</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isReleased</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalStateException</span><span class="p">(</span><span class="s">"Resource has been released"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Use the resource</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Resource is being used"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">release</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">isReleased</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Release the resource</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Resource released"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">isReleased</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">protected</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">finalize</span><span class="p">()</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Throwable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">release</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">super</span><span class="p">.</span><span class="na">finalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Use the Resource Management Class</strong> :</p><p>Ensure the resource is managed correctly by explicitly calling the release method or relying on the finaliser.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">RAIIExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ManagedResource</span><span class="w"/><span class="n">resource</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ManagedResource</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">resource</span><span class="p">.</span><span class="na">useResource</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Perform operations with the resource</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">resource</span><span class="p">.</span><span class="na">release</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Alternatively, without explicit release, the finalizer will handle it</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ManagedResource</span><span class="w"/><span class="n">resource2</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ManagedResource</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">resource2</span><span class="p">.</span><span class="na">useResource</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// No explicit release call</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Explanation</strong> :</p><p><strong>Resource Management Class</strong> : The<code>ManagedResource</code> class acquires the resource during initialisation and provides a<code>useResource</code> method to use it. The<code>release</code> method releases the resource.</p><p><strong>Finalizer</strong> : The<code>finalize</code> method ensures the resource is released when the<code>ManagedResource</code> object is garbage collected if<code>release</code> was not called explicitly.</p><p><strong>Usage</strong> : In the<code>RAIIExample</code> class, the resource is explicitly released in the<code>finally</code> block. For the second resource (<code>resource2</code>), if not explicitly released, the finalizer will handle it.</p><h3 id="important-considerations">Important Considerations:</h3><p><strong>Finalizers</strong> : Using finalizers can be unpredictable, as the timing of garbage collection is not guaranteed. Due to performance implications and unpredictability, it is generally recommended to avoid relying on finalizers for critical resource management.</p><p><strong>Explicit Resource Management</strong> : Whenever possible, explicitly manage resources using well-defined methods or blocks (like try-with-resources) to ensure timely and predictable resource release.</p><p><strong>Java Cleaner API</strong> : For better control over resource management, consider using the Java Cleaner API introduced in Java 9. This API provides a more flexible and reliable alternative to finalizers.</p><h2 id="cleaner-api-example">Cleaner API Example:</h2><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.lang.ref.Cleaner</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ManagedResource</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Cleaner</span><span class="w"/><span class="n">cleaner</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Cleaner</span><span class="p">.</span><span class="na">create</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Cleaner</span><span class="p">.</span><span class="na">Cleanable</span><span class="w"/><span class="n">cleanable</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">isReleased</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">ManagedResource</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">cleanable</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">cleaner</span><span class="p">.</span><span class="na">register</span><span class="p">(</span><span class="k">this</span><span class="p">,</span><span class="w"/><span class="k">this</span><span class="p">::</span><span class="n">release</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Resource acquired"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">useResource</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isReleased</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalStateException</span><span class="p">(</span><span class="s">"Resource has been released"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Resource is being used"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">release</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="n">isReleased</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Resource released"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">isReleased</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">protected</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">finalize</span><span class="p">()</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Throwable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">release</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">super</span><span class="p">.</span><span class="na">finalize</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The Cleaner API provides a more controlled and predictable way to manage resources without relying on the finalisation mechanism. It ensures that resources are cleaned up promptly when the object becomes unreachable.</p><p>Using these techniques, you can effectively manage resources in Java, ensuring they are properly released and avoiding issues like CWE-416.</p><h2 id="more-details-about-javalangrefcleaner-please">More details about java.lang.ref.Cleaner, please</h2><p>The<code>java.lang.ref.Cleaner</code> class, introduced in Java 9, is a modern replacement for the deprecated<code>java.lang.ref.Finalizer</code> mechanism. It provides a more flexible and efficient way to clean up resources when an object becomes unreachable.</p><h3 id="how-to-use-javalangrefcleaner">How to Use<code>java.lang.ref.Cleaner</code>:</h3><p><strong>Create a Cleaner Instance</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">sql</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sql" data-lang="sql"><span class="line"><span class="cl"><span class="w"/><span class="n">Cleaner</span><span class="w"/><span class="n">cleaner</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Cleaner</span><span class="p">.</span><span class="k">create</span><span class="p">();</span></span></span></code></pre></div></div><p><strong>Define a Cleanable Resource</strong> :</p><p>A cleanable resource must implement a<code>Runnable</code> interface to define the cleanup action.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">State</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Runnable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">run</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Cleanup action</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Resource cleaned up"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Register the Resource with Cleaner</strong> :</p><p>Register the resource and its cleanup action with the<code>Cleaner</code>.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">ManagedResource</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Cleaner</span><span class="p">.</span><span class="na">Cleanable</span><span class="w"/><span class="n">cleanable</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">ManagedResource</span><span class="p">(</span><span class="n">Cleaner</span><span class="w"/><span class="n">cleaner</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">State</span><span class="w"/><span class="n">state</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">State</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">cleanable</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">cleaner</span><span class="p">.</span><span class="na">register</span><span class="p">(</span><span class="k">this</span><span class="p">,</span><span class="w"/><span class="n">state</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Resource acquired"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Using the Managed Resource</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">CleanerExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Cleaner</span><span class="w"/><span class="n">cleaner</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Cleaner</span><span class="p">.</span><span class="na">create</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ManagedResource</span><span class="w"/><span class="n">resource</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ManagedResource</span><span class="p">(</span><span class="n">cleaner</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Use the resource...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// No explicit cleanup is required, the Cleaner will handle it</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h3 id="example">Example:</h3><p>Here’s a complete example demonstrating the usage of<code>java.lang.ref.Cleaner</code>:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.lang.ref.Cleaner</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">CleanerExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">class</span><span class="nc">State</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Runnable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">run</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Cleanup action</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Resource cleaned up"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">class</span><span class="nc">ManagedResource</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Cleaner</span><span class="p">.</span><span class="na">Cleanable</span><span class="w"/><span class="n">cleanable</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">ManagedResource</span><span class="p">(</span><span class="n">Cleaner</span><span class="w"/><span class="n">cleaner</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">State</span><span class="w"/><span class="n">state</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">State</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">cleanable</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">cleaner</span><span class="p">.</span><span class="na">register</span><span class="p">(</span><span class="k">this</span><span class="p">,</span><span class="w"/><span class="n">state</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Resource acquired"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Cleaner</span><span class="w"/><span class="n">cleaner</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Cleaner</span><span class="p">.</span><span class="na">create</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ManagedResource</span><span class="w"/><span class="n">resource</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ManagedResource</span><span class="p">(</span><span class="n">cleaner</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Use the resource...</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// No explicit cleanup required, the Cleaner will handle it</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="benefits">Benefits:</h3><p><strong>Predictability</strong> :<code>Cleaner</code> ensures that cleanup actions are executed promptly, unlike finalizers which have unpredictable execution.</p><p><strong>Performance</strong> : Using<code>Cleaner</code> avoids the performance issues associated with finalizers, such as increased GC pressure and potential for memory leaks.</p><p><strong>Simplicity</strong> : The API is straightforward, making it easier to implement and maintain.</p><p>By using<code>java.lang.ref.Cleaner</code>, Java developers can achieve efficient and predictable resource management, minimising risks associated with manual resource cleanup.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Security</category><media:content url="https://svenruppert.com/images/2024/05/B389DB4C-F09B-4E33-8549-37DF3C0F7447.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/05/B389DB4C-F09B-4E33-8549-37DF3C0F7447.jpg"/><enclosure url="https://svenruppert.com/images/2024/05/B389DB4C-F09B-4E33-8549-37DF3C0F7447.jpg" type="image/jpeg" length="0"/></item><item><title>Basics of the Gauss-Krüger Coordinate System</title><link>https://svenruppert.com/posts/basics-of-the-gauss-kruger-coordinate-system/</link><pubDate>Thu, 16 May 2024 14:34:19 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/basics-of-the-gauss-kruger-coordinate-system/</guid><description>Transverse Mercator Projection : The Gauss-Krüger system uses the transverse Mercator projection, which means the cylindrical projection is rotated 90 degrees. This allows for better accuracy over long north-south extents.</description><content:encoded>&lt;![CDATA[<h3 id="transverse-mercator-projection-"><strong>Transverse Mercator Projection</strong> :</h3><p>The Gauss-Krüger system uses the transverse Mercator projection, which means the cylindrical projection is rotated 90 degrees. This allows for better accuracy over long north-south extents.</p><ol><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#transverse-mercator-projection">Transverse Mercator Projection:</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#ellipsoid">Ellipsoid:</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#zones">Zones:</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#coordinates">Coordinates:</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#carl-friedrich-gauss-1777-1855">Carl Friedrich Gauss (1777-1855)</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#johann-heinrich-louis-kruger-1857-1923">Johann Heinrich Louis Krüger (1857-1923)</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#development-of-the-gauss-kruger-coordinate-system">Development of the Gauss-Krüger Coordinate System</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#technical-features">Technical Features</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#modern-developments-and-usage">Modern Developments and Usage</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#step-by-step-conversion-process">Step-by-Step Conversion Process</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#example-conversion">Example Conversion</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#online-tools">Online Tools:</a></li><li><a href="https://svenruppert.com/2024/05/15/basics-of-the-gauss-kruger-coordinate-system/#using-java">Using Java:</a></li></ol><h3 id="ellipsoid-"><strong>Ellipsoid</strong> :</h3><p>The system is based on an ellipsoid model of the Earth, which is more accurate than a spherical model. Different regions might use slightly different ellipsoids, but a common one used in Europe is the Bessel 1841 ellipsoid.</p><h3 id="zones-"><strong>Zones</strong> :</h3><p>The Gauss-Krüger system divides the area into longitudinal zones that are 3° wide. Each zone has its own central meridian. This helps to reduce distortions within each zone. Zone numbering usually starts at a prime meridian (often 9° E or 15° E) and increases by 3° for each zone.</p><h3 id="coordinates-"><strong>Coordinates</strong> :</h3><p>Coordinates are expressed in meters. The system uses false easting and false northing to ensure that all coordinates within a zone are positive.</p><p><strong>Easting (X)</strong> : Measured in meters from the zone&rsquo;s central meridian.</p><p><strong>Northing (Y)</strong> : Measured in meters from the equator.</p><h2 id="accuracy-and-usage-"><strong>Accuracy and Usage</strong> :</h2><p>The Gauss-Krüger system is beneficial for large-scale (detailed) maps due to its high accuracy over short distances. It is commonly used in civil engineering, cadastral mapping, and various geospatial applications within Germany and neighbouring countries.</p><p><strong>Conversion to WGS84</strong> :</p><p>Converting Gauss-Krüger coordinates to the more globally used WGS84 system (used in GPS) requires specific transformation parameters and sometimes complex algorithms due to the differences in ellipsoid and projection methods.</p><p><strong>Software and Tools</strong> :</p><p>Various GIS software (like ArcGIS, QGIS) and online tools can perform these transformations. They typically require inputting the zone number, the easting, and the northing to transform the coordinates accurately.</p><h2 id="history-of-the-gauss-krüger-coordinate-system">History of the Gauss-Krüger Coordinate System</h2><p>The Gauss-Krüger coordinate system has its roots in the early development of geodesy and map projection techniques in the 19th century. Here is a detailed history of the Gauss-Krüger coordinate system:</p><h3 id="carl-friedrich-gauss-1777-1855">Carl Friedrich Gauss (1777-1855)</h3><p><strong>Contribution to Mathematics and Geodesy</strong> :</p><p>Carl Friedrich Gauss, a German mathematician and physicist, made significant contributions to many fields, including geodesy, the science of measuring and understanding the Earth&rsquo;s geometric shape. Gauss developed the mathematical foundations for the projection that bears his name, the transverse Mercator projection, which is essential for creating accurate maps of regions with large north-south extents.</p><p><strong>Transverse Mercator Projection</strong> :</p><p>Gauss&rsquo;s work on the transverse Mercator projection provided a method to project the Earth&rsquo;s surface onto a plane with minimal distortion over relatively small areas. This projection uses a cylinder rotated 90 degrees, touching the Earth along a chosen meridian.</p><h3 id="johann-heinrich-louis-krüger-1857-1923">Johann Heinrich Louis Krüger (1857-1923)</h3><p><strong>Refinement and Application</strong> :</p><p>Johann Heinrich Louis Krüger, a German geodesist, refined Gauss&rsquo;s projection method and applied it to practical mapping needs. Krüger&rsquo;s refinements improved the projection&rsquo;s mathematical accuracy, making it more suitable for detailed surveying and mapping work.</p><h3 id="development-of-the-gauss-krüger-coordinate-system">Development of the Gauss-Krüger Coordinate System</h3><p><strong>Germany and Central Europe</strong> :</p><p>The Gauss-Krüger coordinate system was adopted primarily in Germany and other Central European countries for detailed topographic and cadastral mapping. The system divides the region into longitudinal zones, each 3° wide, with a central meridian. This minimises distortion and ensures high accuracy over small areas.</p><p><strong>Ellipsoid Models</strong> :</p><p>The system uses specific ellipsoid models, such as the Bessel 1841 ellipsoid, which closely approximates the shape of the Earth in these regions.</p><h3 id="technical-features">Technical Features</h3><p><strong>False Easting and Northing</strong> :</p><p>The system applies a false easting and northing to ensure that all coordinates within a zone are positive. Typically, the central meridian is assigned a false easting of 500,000 meters, and the equator is assigned a false northing.</p><p><strong>Zone-Based System</strong> :</p><p>Each zone has its coordinate system, reducing the complexity of calculations and distortions. The zones are numbered, usually starting from a prime meridian (e.g., 9°E) and increasing by 3° for each subsequent zone.</p><h3 id="modern-developments-and-usage">Modern Developments and Usage</h3><p><strong>Integration with Global Systems</strong> :</p><p>With the advent of global positioning systems (GPS) and the universal adoption of the WGS84 ellipsoid, many regions have transitioned to the UTM (Universal Transverse Mercator) system for broader compatibility. However, the Gauss-Krüger system is still used for specific applications requiring high precision and historical continuity in countries like Germany.</p><p><strong>Software and Digital Mapping</strong> :</p><p>Modern GIS (Geographic Information Systems) software supports the Gauss-Krüger projection, allowing for easy conversion between coordinate systems and integration with global datasets.</p><p>The Gauss-Krüger coordinate system is a significant development in the history of cartography and geodesy. It combines the foundational work of Carl Friedrich Gauss and the practical refinements of Johann Heinrich Louis Krüger. Its precise and detailed approach to mapping has made it a valuable tool in Europe, particularly for topographic and cadastral mapping. While global systems like UTM are now more widely used, the Gauss-Krüger system remains integral to geospatial history and practice.</p><h2 id="how-do-you-convert-coordinates-into-utm-coordinates">How do you convert Coordinates into UTM Coordinates?</h2><p>Converting Gauss-Krüger coordinates to UTM (Universal Transverse Mercator) coordinates involves a few steps. Both systems are based on the transverse Mercator projection but use different parameters and zone definitions. Here’s a step-by-step guide on how to perform this conversion:</p><h3 id="step-by-step-conversion-process">Step-by-Step Conversion Process</h3><p><strong>Identify the Gauss-Krüger Zone</strong> :</p><p>Determine the Gauss-Krüger zone of your coordinates. Gauss-Krüger zones are typically 3° wide.</p><p><strong>Central Meridian of Gauss-Krüger Zone</strong> :</p><p>Each Gauss-Krüger zone has a central meridian, which is usually a multiple of 3° (e.g., 9°E, 12°E, 15°E, etc.).</p><p><strong>Translate to Geodetic Coordinates (Latitude and Longitude)</strong> :</p><p>Convert the Gauss-Krüger coordinates (easting and northing) to geodetic coordinates (latitude and longitude). This requires:</p><ul><li>The ellipsoid parameters (e.g., Bessel 1841 for Germany).</li><li>The false easting (usually 500,000 meters).</li><li>Applying the inverse transverse Mercator projection.</li></ul><p><strong>Determine the UTM Zone</strong> :</p><p>Determine the appropriate UTM zone for the longitude obtained from the geodetic coordinates. UTM zones are 6° wide.</p><p><strong>Convert to UTM Coordinates</strong> :</p><p>Convert the geodetic coordinates to UTM coordinates using:</p><ul><li>The WGS84 ellipsoid parameters (commonly used for UTM).</li><li>The UTM zone central meridian.</li><li>Applying the transverse Mercator projection to obtain the UTM easting and northing.</li></ul><h3 id="example-conversion">Example Conversion</h3><p>Let&rsquo;s walk through an example to make it more transparent.</p><p><strong>Given</strong> :</p><p>Gauss-Krüger coordinates: Easting = 3550000 meters, Northing = 5800000 meters. Gauss-Krüger zone central meridian: 12°E (assuming it&rsquo;s in zone 4)</p><p><strong>Translate Gauss-Krüger to Latitude and Longitude</strong> :</p><p>Use the inverse Gauss-Krüger projection to convert (3550000, 5800000) to latitude and longitude. Due to the complexity of the inverse projection, this step typically requires software or detailed formulas.</p><p><strong>Example Result</strong> :</p><p>Let&rsquo;s assume the resulting geodetic coordinates are:</p><ul><li>Latitude: 52.0°N</li><li>Longitude: 13.0°E</li></ul><p><strong>Determine UTM Zone</strong> :</p><p>Longitude 13.0°E falls in UTM zone 33U (UTM zones range from 1 to 60, each 6° wide).</p><p><strong>Convert to UTM Coordinates</strong> :**</p><p>Use the transverse Mercator projection with WGS84 parameters and the central meridian of zone 33 (15°E) to convert latitude 52.0°N and longitude 13.0°E to UTM coordinates.</p><h3 id="online-tools">Online Tools:</h3><p>Websites like<strong><a href="https://epsg.io/">https://epsg.io/</a></strong> or other geospatial transformation tools can also perform these conversions.</p><h3 id="using-java">Using Java:</h3><p>To convert Gauss-Krüger coordinates to UTM coordinates in Java, you can use libraries such as Proj4j, which is a Java port of the “PROJ.4” library (<a href="https://de.wikipedia.org/wiki/PROJ.4">https://de.wikipedia.org/wiki/PROJ.4</a>) used for performing cartographic transformations. Here’s how you can perform the conversion step-by-step:</p><p><strong>Add Proj4j Library to Your Project</strong> :</p><p>If you are using Maven, add the following dependency to your<code>pom.xml</code>:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;groupId&gt;</span>org.locationtech.proj4j<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;artifactId&gt;</span>proj4j<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;version&gt;</span>1.1.0<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><p><strong>Define the Coordinate Reference Systems</strong> :</p><p>You will need to define the Gauss-Krüger and UTM coordinate reference systems.</p><p><strong>Perform the Conversion</strong> :</p><p>Convert Gauss-Krüger coordinates to geodetic coordinates (latitude and longitude). Convert the geodetic coordinates to UTM coordinates.</p><p>Here is an example Java program that demonstrates this process:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.locationtech.proj4j.CRSFactory</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.locationtech.proj4j.CoordinateReferenceSystem</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.locationtech.proj4j.ProjCoordinate</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.locationtech.proj4j.Projection</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.locationtech.proj4j.ProjectionFactory</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">org.locationtech.proj4j.ProjectionTransform</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">GaussKrugerToUTMConverter</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a CRSFactory instance</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">CRSFactory</span><span class="w"/><span class="n">factory</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">CRSFactory</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Define the Gauss-Krüger CRS (EPSG:31468 for zone 4)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">CoordinateReferenceSystem</span><span class="w"/><span class="n">gaussKrugerCRS</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">factory</span><span class="p">.</span><span class="na">createFromName</span><span class="p">(</span><span class="s">"EPSG:31468"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Define the UTM CRS (EPSG:32633 for UTM zone 33N)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">CoordinateReferenceSystem</span><span class="w"/><span class="n">utmCRS</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">factory</span><span class="p">.</span><span class="na">createFromName</span><span class="p">(</span><span class="s">"EPSG:32633"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Define the source coordinates in Gauss-Krüger (example values)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">double</span><span class="w"/><span class="n">gkEasting</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">3550000</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">double</span><span class="w"/><span class="n">gkNorthing</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">5800000</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a ProjCoordinate object for the input coordinates</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ProjCoordinate</span><span class="w"/><span class="n">gkCoord</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ProjCoordinate</span><span class="p">(</span><span class="n">gkEasting</span><span class="p">,</span><span class="w"/><span class="n">gkNorthing</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a ProjCoordinate object for the intermediate geodetic coordinates</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ProjCoordinate</span><span class="w"/><span class="n">geoCoord</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ProjCoordinate</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a ProjectionTransform object to convert from Gauss-Krüger to geodetic</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ProjectionTransform</span><span class="w"/><span class="n">gkToGeoTransform</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ProjectionTransform</span><span class="p">(</span><span class="n">gaussKrugerCRS</span><span class="p">.</span><span class="na">getProjection</span><span class="p">(),</span><span class="w"/><span class="n">ProjectionFactory</span><span class="p">.</span><span class="na">getGeographic</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Transform Gauss-Krüger to geodetic coordinates (longitude, latitude)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">gkToGeoTransform</span><span class="p">.</span><span class="na">transform</span><span class="p">(</span><span class="n">gkCoord</span><span class="p">,</span><span class="w"/><span class="n">geoCoord</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Print the geodetic coordinates (longitude, latitude)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Geodetic coordinates: Longitude = "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">geoCoord</span><span class="p">.</span><span class="na">x</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">", Latitude = "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">geoCoord</span><span class="p">.</span><span class="na">y</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a ProjCoordinate object for the final UTM coordinates</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ProjCoordinate</span><span class="w"/><span class="n">utmCoord</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ProjCoordinate</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a ProjectionTransform object to convert from geodetic to UTM</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ProjectionTransform</span><span class="w"/><span class="n">geoToUtmTransform</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ProjectionTransform</span><span class="p">(</span><span class="n">ProjectionFactory</span><span class="p">.</span><span class="na">getGeographic</span><span class="p">(),</span><span class="w"/><span class="n">utmCRS</span><span class="p">.</span><span class="na">getProjection</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Transform geodetic coordinates to UTM coordinates</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">geoToUtmTransform</span><span class="p">.</span><span class="na">transform</span><span class="p">(</span><span class="n">geoCoord</span><span class="p">,</span><span class="w"/><span class="n">utmCoord</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Print the UTM coordinates (easting, northing)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"UTM coordinates: Easting = "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">utmCoord</span><span class="p">.</span><span class="na">x</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">", Northing = "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">utmCoord</span><span class="p">.</span><span class="na">y</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Dependencies</strong> :</p><p>The<code>proj4j</code> library is added as a dependency to handle coordinate transformations.</p><p><strong>Coordinate Reference Systems (CRS)</strong> :</p><p>The Gauss-Krüger CRS is defined using<code>EPSG:31468</code> for zone 4. The UTM CRS is defined using<code>EPSG:32633</code> for UTM zone 33N.</p><p><strong>Transformation Process</strong> :</p><p>A<code>ProjCoordinate</code> object is created for the input Gauss-Krüger coordinates. The Gauss-Krüger coordinates are transformed into geodetic coordinates (longitude and latitude). The geodetic coordinates are then transformed to UTM coordinates.</p><p><strong>Notes</strong> :</p><p>Ensure the<code>epsg</code> codes are correct for your specific regions and projections. The actual geodetic transformation can be complex due to ellipsoid differences, so precision might require specific parameters and fine-tuning.</p>
]]></content:encoded><category>Navigation</category><media:content url="https://svenruppert.com/images/2024/05/842F61C8-D48F-4489-8ACA-A63B5FBD6C62.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/05/842F61C8-D48F-4489-8ACA-A63B5FBD6C62.jpg"/><enclosure url="https://svenruppert.com/images/2024/05/842F61C8-D48F-4489-8ACA-A63B5FBD6C62.jpg" type="image/jpeg" length="0"/></item><item><title>CWE-787 - The Bird-Eye View for Java Developers</title><link>https://svenruppert.com/posts/cwe-787-the-bird-eye-view-for-java-developers/</link><pubDate>Wed, 15 May 2024 12:19:10 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/cwe-787-the-bird-eye-view-for-java-developers/</guid><description>The term &ldquo;CWE-787: Out-of-bounds Write " likely refers to a specific security vulnerability or error in software systems. Let&rsquo;s break down what it means:
Out-of-bounds Write : This is a type of vulnerability where a program writes data outside the boundaries of pre-allocated fixed-length buffers. This can corrupt data, crash the program, or lead to the execution of malicious code.</description><content:encoded>&lt;![CDATA[<p>The term &ldquo;<strong>CWE-787: Out-of-bounds Write</strong> " likely refers to a specific security vulnerability or error in software systems. Let&rsquo;s break down what it means:</p><p><strong>Out-of-bounds Write</strong> : This is a type of vulnerability where a program writes data outside the boundaries of pre-allocated fixed-length buffers. This can corrupt data, crash the program, or lead to the execution of malicious code.</p><p><strong>CWE-787</strong> : This is a specific identifier for the vulnerability. Identifiers like these are often used in vulnerability databases or bug-tracking systems. They help uniquely identify and reference a particular issue.</p><ol><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#explanation-of-out-of-bounds-write">Explanation of Out-of-bounds Write</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#impact-of-out-of-bounds-write">Impact of Out-of-bounds Write</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#examples-and-mitigation">Examples and Mitigation</a><ol><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#example-in-c">Example in C:</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#mitigation">Mitigation:</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#example-of-safe-code">Example of Safe Code:</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#and-how-is-it-done-in-java">And how is it done in Java?</a><ol><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#understanding-out-of-bounds-write-in-java">Understanding Out-of-bounds Write in Java</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#example-of-out-of-bounds-write">Example of Out-of-bounds Write</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#mitigation-strategies">Mitigation Strategies</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#corrected-example">Corrected Example</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#common-causes-of-out-of-bounds-write-in-java">Common Causes of Out-of-bounds Write in Java</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#advanced-example">Advanced Example</a><ol><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#mitigated-version">Mitigated Version</a></li></ol></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#cwe-787-based-on-offheap-techniques-in-java-8">CWE 787 - Based on OffHeap techniques in Java 8</a><ol><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#off-heap-memory-management-in-java">Off-heap Memory Management in Java</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#example-of-out-of-bounds-write-with-off-heap-memory">Example of Out-of-bounds Write with Off-heap Memory</a><ol><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#using-sun-misc-unsafe">Using<code>sun.misc.Unsafe</code></a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#mitigating-out-of-bounds-write-with-off-heap-memory">Mitigating Out-of-bounds Write with Off-heap Memory</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#corrected-example-with-sun-misc-unsafe">Corrected Example with<code>sun.misc.Unsafe</code></a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#using-java-nio-bytebuffer">Using<code>java.nio.ByteBuffer</code></a></li></ol></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#handling-larger-data-structures">Handling Larger Data Structures</a><ol><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#example-with-intbuffer">Example with<code>IntBuffer</code></a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#explanation">Explanation</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#offheap-usage-in-java-21">OffHeap usage in Java 21</a><ol><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#critical-features-for-off-heap-usage-in-java-21">Critical Features for Off-Heap Usage in Java 21</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#foreign-function-memory-api">Foreign Function &amp; Memory API</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#example-using-foreign-function-memory-api">Example Using Foreign Function &amp; Memory API</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#enhanced-bytebuffer-operations">Enhanced<code>ByteBuffer</code> Operations</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#memory-segments-and-access-varhandles">Memory Segments and Access VarHandles</a><ol><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#example-using-memory-segments">Example Using Memory Segments</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#differences-in-offheap-usage-between-java-17-and-java-21">Differences in OffHeap usage between Java 17 and Java 21</a><ol><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#foreign-function-memory-api">Foreign Function &amp; Memory API</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#enhanced-bytebuffer-operations">Enhanced ByteBuffer Operations</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#memory-segments-and-varhandles">Memory Segments and VarHandles</a></li><li><a href="https://svenruppert.com/2024/05/15/cwe-787-the-bird-eye-view-for-java-developers/#summary-of-differences">Summary of Differences</a></li></ol></li></ol><h2 id="explanation-of-out-of-bounds-write">Explanation of Out-of-bounds Write</h2><p>An out-of-bounds write occurs when a program writes data past the end, or before the beginning, of the buffer it was allocated. This usually happens due to programming errors such as:</p><ul><li>Incorrectly calculating buffer sizes.</li><li>Failing to check the bounds before writing data.</li><li>Using unsafe functions that do not perform bounds checking.</li></ul><h2 id="impact-of-out-of-bounds-write">Impact of Out-of-bounds Write</h2><p>The consequences of an out-of-bounds write can be severe, including:</p><p><strong>Data Corruption</strong> : Overwriting adjacent memory locations can corrupt other data.</p><p><strong>Program Crashes</strong> : Writing to illegal memory addresses can cause the program to crash.</p><p><strong>Security Vulnerabilities</strong> : Attackers can exploit these vulnerabilities to execute arbitrary code, leading to potential breaches, data leaks, or system compromise.</p><h2 id="examples-and-mitigation">Examples and Mitigation</h2><h3 id="example-in-c">Example in C:</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">#include<span class="nt">&lt;string.h&gt;</span></span></span><span class="line"><span class="cl">#include<span class="nt">&lt;stdio.h&gt;</span></span></span><span class="line"><span class="cl">int main() {</span></span><span class="line"><span class="cl">    char buffer[10];</span></span><span class="line"><span class="cl">    strcpy(buffer, "This string is too long for the buffer");</span></span><span class="line"><span class="cl">    printf("%s\n", buffer);</span></span><span class="line"><span class="cl">    return 0;</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>In this example, the string copied into<code>buffer</code> exceeds its allocated size, causing an out-of-bounds write.</p><h3 id="mitigation">Mitigation:</h3><p><strong>Bounds Checking</strong> : Ensure that any write operations do not exceed the allocated buffer size.</p><p><strong>Use Safe Functions</strong> : Utilise safer library functions like<code>strncpy</code> instead of<code>strcpy</code>.</p><p><strong>Static Analysis</strong> : Use static analysis tools to detect out-of-bounds writes during development.</p><p><strong>Runtime Protection</strong> : Employ runtime protections such as address space layout randomisation (ASLR) and stack canaries.</p><h3 id="example-of-safe-code">Example of Safe Code:</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">#include<span class="nt">&lt;string.h&gt;</span></span></span><span class="line"><span class="cl">#include<span class="nt">&lt;stdio.h&gt;</span></span></span><span class="line"><span class="cl">int main() {</span></span><span class="line"><span class="cl">    char buffer[10];</span></span><span class="line"><span class="cl">    strncpy(buffer, "This string is too long for the buffer", sizeof(buffer) - 1);</span></span><span class="line"><span class="cl">    buffer[sizeof(buffer) - 1] = '\0';  // Ensure null termination</span></span><span class="line"><span class="cl">    printf("%s\n", buffer);</span></span><span class="line"><span class="cl">    return 0;</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>In this corrected version,<code>strncpy</code> ensures that no more than the allocated size of<code>buffer</code> is written, and null-terminating the string prevents buffer overflow.</p><p>Understanding and preventing out-of-bounds writes is crucial in developing secure and stable software. Proper coding practices, safe functions, and security measures can mitigate these vulnerabilities effectively.</p><h2 id="and-how-is-it-done-in-java">And how is it done in Java?</h2><p>Out-of-bounds write vulnerabilities are less common in Java than in languages like C or C++ due to built-in safety features, such as automatic bounds checking for arrays. However, certain situations can still lead to such vulnerabilities, often related to misusing arrays or improper handling of indices.</p><h3 id="understanding-out-of-bounds-write-in-java">Understanding Out-of-bounds Write in Java</h3><p>In Java, out-of-bounds write errors typically occur when you try to access an array or a list with an index that is either negative or greater than or equal to the size of the array or list. This usually results in an<code>ArrayIndexOutOfBoundsException</code>.</p><h3 id="example-of-out-of-bounds-write">Example of Out-of-bounds Write</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">OutOfBoundsWriteExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">int</span><span class="o">[]</span><span class="w"/><span class="n">numbers</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">int</span><span class="o">[</span><span class="n">5</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;=</span><span class="w"/><span class="n">numbers</span><span class="p">.</span><span class="na">length</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="c1">// Off-by-one error</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">numbers</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">2</span><span class="p">;</span><span class="w"/><span class="c1">// This will throw an ArrayIndexOutOfBoundsException</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the loop runs from<code>0</code> to<code>numbers.length</code>, one past the last valid index. This causes an<code>ArrayIndexOutOfBoundsException</code>.</p><h3 id="mitigation-strategies">Mitigation Strategies</h3><p><strong>Proper Bounds Checking</strong> : Always ensure that any access to arrays or lists is within valid bounds.</p><p><strong>Enhanced for Loop</strong> : Use enhanced for loops when possible to avoid index errors.</p><p><strong>Libraries and Functions</strong> : Use Java&rsquo;s standard libraries and methods that handle bounds checking automatically.</p><h3 id="corrected-example">Corrected Example</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">OutOfBoundsWriteExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">int</span><span class="o">[]</span><span class="w"/><span class="n">numbers</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">int</span><span class="o">[</span><span class="n">5</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">numbers</span><span class="p">.</span><span class="na">length</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="c1">// Correct bounds</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">numbers</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">2</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">number</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">numbers</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">number</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this corrected version, the loop runs from<code>0</code> to<code>numbers.length—1</code>, which prevents the<code>ArrayIndexOutOfBoundsException</code>.</p><h2 id="common-causes-of-out-of-bounds-write-in-java">Common Causes of Out-of-bounds Write in Java</h2><p><strong>Off-by-one Errors</strong> : These occur when loops iterate one time too many or too few.</p><p><strong>Incorrect Index Calculation</strong> : When the calculation of an index is erroneous due to logic errors.</p><p><strong>Manual Array Management</strong> : Errors often happen when manipulating arrays directly rather than using collections like<code>ArrayList</code>.</p><h2 id="advanced-example">Advanced Example</h2><p>Consider a scenario where an attempt to manage a buffer directly leads to an out-of-bounds write:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">BufferOverflowExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">int</span><span class="o">[]</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">int</span><span class="o">[</span><span class="n">10</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">writeToBuffer</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span><span class="w"/><span class="n">10</span><span class="p">,</span><span class="w"/><span class="n">42</span><span class="p">);</span><span class="w"/><span class="c1">// Trying to write outside the buffer</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">ArrayIndexOutOfBoundsException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Caught exception: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">writeToBuffer</span><span class="p">(</span><span class="kt">int</span><span class="o">[]</span><span class="w"/><span class="n">buffer</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">index</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">value</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">buffer</span><span class="o">[</span><span class="n">index</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">value</span><span class="p">;</span><span class="w"/><span class="c1">// Potential out-of-bounds write</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example,<code>writeToBuffer</code> is called with an index that is out of bounds, leading to an<code>ArrayIndexOutOfBoundsException</code>.</p><h3 id="mitigated-version">Mitigated Version</h3><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">BufferOverflowExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">int</span><span class="o">[]</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">int</span><span class="o">[</span><span class="n">10</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">writeToBuffer</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span><span class="w"/><span class="n">10</span><span class="p">,</span><span class="w"/><span class="n">42</span><span class="p">);</span><span class="w"/><span class="c1">// Trying to write outside the buffer</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IllegalArgumentException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Caught exception: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">writeToBuffer</span><span class="p">(</span><span class="kt">int</span><span class="o">[]</span><span class="w"/><span class="n">buffer</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">index</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">value</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">index</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">0</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">index</span><span class="w"/><span class="o">&gt;=</span><span class="w"/><span class="n">buffer</span><span class="p">.</span><span class="na">length</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">IllegalArgumentException</span><span class="p">(</span><span class="s">"Index out of bounds: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">index</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">buffer</span><span class="o">[</span><span class="n">index</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">value</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here,<code>writeToBuffer</code> checks the index before writing to the buffer, preventing an out-of-bounds write.</p><p>While Java provides many safeguards against out-of-bounds writes, developers must still practice diligent bounds checking and utilise safe coding practices to prevent these errors. Handling array indices and leveraging Java&rsquo;s robust standard libraries can help maintain secure and stable code.</p><h2 id="cwe-787---based-on-offheap-techniques-in-java-8">CWE 787 - Based on OffHeap techniques in Java 8</h2><p>&ldquo;off-heap&rdquo; in Java refers to memory allocated outside the Java heap, typically managed by native code. Working with off-heap memory can provide performance benefits, especially for large data sets or when interacting with native libraries. Still, it also introduces risks such as out-of-bounds writes.</p><h3 id="off-heap-memory-management-in-java">Off-heap Memory Management in Java</h3><p>Java provides several ways to work with off-heap memory, including the<code>sun.misc.Unsafe</code> class and<code>java.nio.ByteBuffer</code>. The<code>sun.misc.Unsafe</code> class allows low-level, unsafe operations, and should be used with caution. The<code>java.nio.ByteBuffer</code> class is safer but requires careful bounds checking.</p><h3 id="example-of-out-of-bounds-write-with-off-heap-memory">Example of Out-of-bounds Write with Off-heap Memory</h3><h4 id="using-sunmiscunsafe">Using<code>sun.misc.Unsafe</code></h4><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">sun.misc.Unsafe</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.lang.reflect.Field</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">OffHeapExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Unsafe</span><span class="w"/><span class="n">unsafe</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">BYTE_ARRAY_BASE_OFFSET</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">static</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Field</span><span class="w"/><span class="n">field</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Unsafe</span><span class="p">.</span><span class="na">class</span><span class="p">.</span><span class="na">getDeclaredField</span><span class="p">(</span><span class="s">"theUnsafe"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">field</span><span class="p">.</span><span class="na">setAccessible</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">unsafe</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">Unsafe</span><span class="p">)</span><span class="w"/><span class="n">field</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">BYTE_ARRAY_BASE_OFFSET</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">unsafe</span><span class="p">.</span><span class="na">arrayBaseOffset</span><span class="p">(</span><span class="kt">byte</span><span class="o">[]</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RuntimeException</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">long</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">10</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">long</span><span class="w"/><span class="n">memoryAddress</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">unsafe</span><span class="p">.</span><span class="na">allocateMemory</span><span class="p">(</span><span class="n">size</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;=</span><span class="w"/><span class="n">size</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="c1">// Intentional off-by-one error</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">unsafe</span><span class="p">.</span><span class="na">putByte</span><span class="p">(</span><span class="n">memoryAddress</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">i</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">i</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">unsafe</span><span class="p">.</span><span class="na">freeMemory</span><span class="p">(</span><span class="n">memoryAddress</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the loop iterates one time too many, causing an out-of-bounds write when<code>i</code> equals<code>size</code>.</p><h4 id="mitigating-out-of-bounds-write-with-off-heap-memory">Mitigating Out-of-bounds Write with Off-heap Memory</h4><p><strong>Bounds Checking</strong> : Always ensure that memory accesses are within the allocated bounds.</p><p><strong>Abstraction</strong> : Use higher-level abstractions that handle bounds checking automatically, such as<code>java.nio.ByteBuffer</code>.</p><h4 id="corrected-example-with-sunmiscunsafe">Corrected Example with<code>sun.misc.Unsafe</code></h4><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">sun.misc.Unsafe</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.lang.reflect.Field</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">OffHeapExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">Unsafe</span><span class="w"/><span class="n">unsafe</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">BYTE_ARRAY_BASE_OFFSET</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">static</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">Field</span><span class="w"/><span class="n">field</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Unsafe</span><span class="p">.</span><span class="na">class</span><span class="p">.</span><span class="na">getDeclaredField</span><span class="p">(</span><span class="s">"theUnsafe"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">field</span><span class="p">.</span><span class="na">setAccessible</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">unsafe</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">Unsafe</span><span class="p">)</span><span class="w"/><span class="n">field</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">BYTE_ARRAY_BASE_OFFSET</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">unsafe</span><span class="p">.</span><span class="na">arrayBaseOffset</span><span class="p">(</span><span class="kt">byte</span><span class="o">[]</span><span class="p">.</span><span class="na">class</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">Exception</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RuntimeException</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">long</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">10</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">long</span><span class="w"/><span class="n">memoryAddress</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">unsafe</span><span class="p">.</span><span class="na">allocateMemory</span><span class="p">(</span><span class="n">size</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">size</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="c1">// Correct bounds</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">unsafe</span><span class="p">.</span><span class="na">putByte</span><span class="p">(</span><span class="n">memoryAddress</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">i</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">i</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">unsafe</span><span class="p">.</span><span class="na">freeMemory</span><span class="p">(</span><span class="n">memoryAddress</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Here, the loop correctly iterates from<code>0</code> to<code>size - 1</code>, preventing the out-of-bounds write.</p><h4 id="using-javaniobytebuffer">Using<code>java.nio.ByteBuffer</code></h4><p>A safer alternative involves using<code>java.nio.ByteBuffer</code> for off-heap memory management. ByteBuffer provides bounds checking, reducing the risk of out-of-bounds writes.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.ByteBuffer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">OffHeapByteBufferExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">int</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">10</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">ByteBuffer</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ByteBuffer</span><span class="p">.</span><span class="na">allocateDirect</span><span class="p">(</span><span class="n">size</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">size</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="c1">// Correct bounds</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">buffer</span><span class="p">.</span><span class="na">put</span><span class="p">((</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">i</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">buffer</span><span class="p">.</span><span class="na">flip</span><span class="p">();</span><span class="w"/><span class="c1">// Prepare the buffer for reading</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">while</span><span class="w"/><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="na">hasRemaining</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">buffer</span><span class="p">.</span><span class="na">get</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example,<code>ByteBuffer</code> manages off-heap memory safely with built-in bounds checking.</p><p>Managing off-heap memory in Java can offer performance benefits but requires careful handling to avoid out-of-bounds writes. Using<code>sun.misc.Unsafe</code> provides powerful capabilities but comes with significant risks. For safer memory management,<code>java.nio.ByteBuffer</code> is recommended due to its automatic bounds checking. Always perform thorough bounds checking and prefer higher-level abstractions to ensure memory safety. But there are changes in newer Java releases.</p><h3 id="handling-larger-data-structures">Handling Larger Data Structures</h3><p>For more complex data structures, you can use methods like<code>asIntBuffer</code>,<code>asLongBuffer</code>, etc., which provide views of the buffer with different data types.</p><h4 id="example-with-intbuffer">Example with<code>IntBuffer</code></h4><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.ByteBuffer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.nio.IntBuffer</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">OffHeapIntBufferExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kt">int</span><span class="w"/><span class="n">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">10</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Allocate off-heap memory</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">ByteBuffer</span><span class="w"/><span class="n">byteBuffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ByteBuffer</span><span class="p">.</span><span class="na">allocateDirect</span><span class="p">(</span><span class="n">size</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">Integer</span><span class="p">.</span><span class="na">BYTES</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">IntBuffer</span><span class="w"/><span class="n">intBuffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">byteBuffer</span><span class="p">.</span><span class="na">asIntBuffer</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Writing data to the buffer</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">size</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">intBuffer</span><span class="p">.</span><span class="na">put</span><span class="p">(</span><span class="n">i</span><span class="p">);</span><span class="w"/><span class="c1">// Proper bounds checking is inherent</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Preparing the buffer to read the data</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">intBuffer</span><span class="p">.</span><span class="na">flip</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// Reading data from the buffer</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">while</span><span class="w"/><span class="p">(</span><span class="n">intBuffer</span><span class="p">.</span><span class="na">hasRemaining</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">intBuffer</span><span class="p">.</span><span class="na">get</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h4 id="explanation">Explanation</h4><p><strong>Allocation</strong> :<code>ByteBuffer.allocateDirect(size * Integer.BYTES)</code> allocates a direct buffer with enough space for<code>size</code> integers.</p><p><strong>IntBuffer View</strong> :<code>byteBuffer.asIntBuffer()</code> creates an<code>IntBuffer</code> view of the<code>ByteBuffer</code>, directly allowing you to work with integers.</p><p><strong>Writing and Reading</strong> : The<code>put</code> and<code>get</code> methods of<code>IntBuffer</code> are used for writing and reading integers, with bounds checking.</p><p>Using<code>java.nio.ByteBuffer</code> for off-heap memory management in Java provides a safer alternative to<code>sun.misc.Unsafe</code>, ensuring automatic bounds checking and reducing the risk of out-of-bounds writes. Following the outlined examples and strategies, you can effectively manage off-heap memory while maintaining code safety and integrity.</p><h2 id="offheap-usage-in-java-21">OffHeap usage in Java 21</h2><p>Java 21 introduces several enhancements and features for off-heap memory management, focusing on improved performance, safety, and ease of use. While the core approach using<code>java.nio.ByteBuffer</code> remains prevalent, new features and improvements in the Java ecosystem help with off-heap usage.</p><h3 id="critical-features-for-off-heap-usage-in-java-21">Critical Features for Off-Heap Usage in Java 21</h3><p><strong>Foreign Function &amp; Memory API (Preview)</strong>: Java 21 includes a preview of the Foreign Function &amp; Memory API, designed to facilitate interactions with native code and memory. This API allows for safe and efficient off-heap memory access and manipulation.</p><p><strong>Enhanced<code>ByteBuffer</code> Operations</strong> : The<code>ByteBuffer</code> class has been optimised and extended with more operations, making it more efficient for handling off-heap memory.</p><p><strong>Memory Segments and Access VarHandles</strong> : Java 21 introduces memory segments and access varhandles for structured memory access. This provides a safer and more structured way to handle off-heap memory.</p><h3 id="foreign-function--memory-api">Foreign Function &amp; Memory API</h3><p>The Foreign Function &amp; Memory API allows Java programs to interoperate with native libraries and manage off-heap memory more safely and efficiently. This API includes classes such as<code>MemorySegment</code>,<code>MemoryAddress</code>, and<code>MemoryLayout</code> to represent and manipulate memory.</p><h3 id="example-using-foreign-function--memory-api">Example Using Foreign Function &amp; Memory API</h3><p>Here is an example of how you might use the Foreign Function &amp; Memory API to allocate and manipulate off-heap memory:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">jdk.incubator.foreign.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">OffHeapExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Allocate 100 bytes of off-heap memory</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">MemorySegment</span><span class="w"/><span class="n">segment</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MemorySegment</span><span class="p">.</span><span class="na">allocateNative</span><span class="p">(</span><span class="n">100</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">MemoryAddress</span><span class="w"/><span class="n">baseAddress</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">segment</span><span class="p">.</span><span class="na">baseAddress</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Write data to off-heap memory</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">100</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">segment</span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="n">ValueLayout</span><span class="p">.</span><span class="na">JAVA_BYTE</span><span class="p">,</span><span class="w"/><span class="n">i</span><span class="p">,</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="n">i</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Read data from off-heap memory</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">100</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kt">byte</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">segment</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">ValueLayout</span><span class="p">.</span><span class="na">JAVA_BYTE</span><span class="p">,</span><span class="w"/><span class="n">i</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Value at index "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">": "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">value</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="enhanced-bytebuffer-operations">Enhanced<code>ByteBuffer</code> Operations</h3><p>While<code>ByteBuffer</code> remains a crucial class for off-heap memory management, Java 21&rsquo;s performance improvements make it even more suitable for high-performance applications.</p><h3 id="memory-segments-and-access-varhandles">Memory Segments and Access VarHandles</h3><p>The new memory segment API provides a safer and more structured way to handle off-heap memory, replacing the need for<code>sun.misc.Unsafe</code>.</p><h4 id="example-using-memory-segments">Example Using Memory Segments</h4><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">jdk.incubator.foreign.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MemorySegmentExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Allocate 10 integers worth of off-heap memory</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">MemorySegment</span><span class="w"/><span class="n">segment</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MemorySegment</span><span class="p">.</span><span class="na">allocateNative</span><span class="p">(</span><span class="n">10</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">4</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="n">VarHandle</span><span class="w"/><span class="n">intHandle</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">MemoryHandles</span><span class="p">.</span><span class="na">varHandle</span><span class="p">(</span><span class="kt">int</span><span class="p">.</span><span class="na">class</span><span class="p">,</span><span class="w"/><span class="n">ByteOrder</span><span class="p">.</span><span class="na">nativeOrder</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Writing data to the memory segment</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">10</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">intHandle</span><span class="p">.</span><span class="na">set</span><span class="p">(</span><span class="n">segment</span><span class="p">.</span><span class="na">baseAddress</span><span class="p">().</span><span class="na">addOffset</span><span class="p">(</span><span class="n">i</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">4</span><span class="p">),</span><span class="w"/><span class="n">i</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="c1">// Reading data from the memory segment</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">10</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="kt">int</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="p">)</span><span class="w"/><span class="n">intHandle</span><span class="p">.</span><span class="na">get</span><span class="p">(</span><span class="n">segment</span><span class="p">.</span><span class="na">baseAddress</span><span class="p">().</span><span class="na">addOffset</span><span class="p">(</span><span class="n">i</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">4</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">                </span><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Value at index "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">": "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">value</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">            </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Java 21 enhances off-heap memory management with new and improved features such as the Foreign Function &amp; Memory API and enhanced<code>ByteBuffer</code> operations. These features provide safer, more efficient, and more flexible ways to interact with off-heap memory. By leveraging these new capabilities, developers can write high-performance, memory-safe applications that interact seamlessly with native libraries and off-heap memory.</p><h2 id="differences-in-offheap-usage-between-java-17-and-java-21">Differences in OffHeap usage between Java 17 and Java 21</h2><p>Java 21 introduces several enhancements and new features for off-heap memory management compared to Java 17. Here’s a detailed comparison highlighting the key differences:</p><h3 id="foreign-function--memory-api-1">Foreign Function &amp; Memory API</h3><p><strong>Java 17</strong> :</p><p><strong>Incubator Stage</strong> : The Foreign Function &amp; Memory API was in an incubator stage, which was experimental and subject to change. The API provided the foundations for off-heap memory access and foreign function calls but lacked maturity and stability.</p><p><strong>Java 21</strong> :</p><p><strong>Preview Stage</strong> : The Foreign Function &amp; Memory API is now in a preview stage, indicating it has become more stable and mature. This API allows for safer, more efficient off-heap memory management and interoperability with native libraries.</p><p><strong>MemorySegment and MemoryAddress</strong> : These abstractions provide structured and safe access to off-heap memory, reducing the risks associated with manual memory management.</p><p><strong>MemoryLayout and VarHandles</strong> : These additions enable developers to describe the layout of memory segments and access them in a type-safe manner using varhandles.</p><h3 id="enhanced-bytebuffer-operations-1">Enhanced ByteBuffer Operations</h3><p><strong>Java 17</strong> :</p><p><strong>Basic Operations</strong> : The<code>ByteBuffer</code> class in Java 17 supports basic operations for off-heap memory allocation and access through<code>allocateDirect</code>.</p><p><strong>Performance</strong> : While effective, the performance optimisations were less advanced than in Java 21.</p><p><strong>Java 21</strong> :</p><p><strong>Performance Improvements</strong> : Java 21 includes performance optimisations for<code>ByteBuffer</code>, making it more efficient for high-performance applications.</p><p><strong>Extended Operations</strong> : Additional operations and methods may have been introduced or optimised to enhance functionality and ease of use.</p><h3 id="memory-segments-and-varhandles">Memory Segments and VarHandles</h3><p><strong>Java 17</strong> :</p><p><strong>Limited Usage</strong> : Memory segments and varhandles were part of the incubator Foreign Memory Access API, not widely adopted or stable.</p><p><strong>Java 21</strong> :</p><p><strong>Stabilised and Enhanced</strong> : These concepts have been further developed and stabilised, providing a more robust way to handle off-heap memory.</p><p><strong>Structured Access</strong> : Memory segments offer a structured way to allocate, access, and manage off-heap memory, reducing risks and improving safety.</p><p><strong>VarHandles</strong> : Provide a type-safe mechanism to manipulate memory, akin to low-level pointers but with safety checks.</p><h3 id="summary-of-differences">Summary of Differences</h3><p><strong>API Maturity</strong> : The Foreign Function &amp; Memory API has evolved from an incubator stage in Java 17 to a preview stage in Java 21, providing a more stable and feature-rich environment.</p><p><strong>Performance</strong> : Java 21 offers performance improvements and more optimised operations for<code>ByteBuffer</code> and other memory-related classes.</p><p><strong>Memory Management</strong> : Java 21 introduces more structured and safer ways to handle off-heap memory with memory segments and varhandles.</p><p><strong>Safety and Efficiency</strong> : Java 21&rsquo;s improvements emphasise safety and efficiency, reducing the risks associated with manual off-heap memory management.</p><p>These advancements in Java 21 enhance the capabilities and safety of off-heap memory management, making it a more robust choice for developers needing high-performance memory operations.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Security</category><media:content url="https://svenruppert.com/images/2024/05/1283FC1B-268C-43CD-8DE0-B44536BEA98D.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/05/1283FC1B-268C-43CD-8DE0-B44536BEA98D.jpg"/><enclosure url="https://svenruppert.com/images/2024/05/1283FC1B-268C-43CD-8DE0-B44536BEA98D.jpg" type="image/jpeg" length="0"/></item><item><title>Mastering Secure Error Handling in Java: Best Practices and Strategies</title><link>https://svenruppert.com/posts/mastering-secure-error-handling-in-java-best-practices-and-strategies/</link><pubDate>Tue, 07 May 2024 12:43:21 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/mastering-secure-error-handling-in-java-best-practices-and-strategies/</guid><description>What is ErrorHandling? Error handling refers to the programming practice of anticipating, detecting, and responding to exceptions or errors in software during its execution. Errors may occur for various reasons, such as invalid user inputs, hardware failures, or bugs in the code. Proper error handling helps ensure that the program can handle such situations gracefully by resolving the Error, compensating for it, or failing safely.</description><content:encoded>&lt;![CDATA[<h2 id="what-is-errorhandling">What is ErrorHandling?</h2><p>Error handling refers to the programming practice of anticipating, detecting, and responding to exceptions or errors in software during its execution. Errors may occur for various reasons, such as invalid user inputs, hardware failures, or bugs in the code. Proper error handling helps ensure that the program can handle such situations gracefully by resolving the Error, compensating for it, or failing safely.</p><ol><li><a href="https://svenruppert.com/?p=1528#what-is-errorhandling">What is ErrorHandling?</a></li><li><a href="https://svenruppert.com/?p=1528#how-is-error-handling-done-in-java">How is error handling done in Java?</a><ol><li><a href="https://svenruppert.com/?p=1528#types-of-exceptions">Types of Exceptions</a></li><li><a href="https://svenruppert.com/?p=1528#exception-handling-mechanisms">Exception Handling Mechanisms</a><ol><li><a href="https://svenruppert.com/?p=1528#try-catch-block">Try-Catch Block</a></li><li><a href="https://svenruppert.com/?p=1528#throws-keyword">Throws Keyword</a></li><li><a href="https://svenruppert.com/?p=1528#throw-keyword">Throw Keyword</a></li></ol></li><li><a href="https://svenruppert.com/?p=1528#custom-exceptions">Custom Exceptions</a></li><li><a href="https://svenruppert.com/?p=1528#best-practices">Best Practices</a></li></ol></li><li><a href="https://svenruppert.com/?p=1528#why-is-error-handling-necessary-for-security">Why is Error handling necessary for security?</a></li><li><a href="https://svenruppert.com/?p=1528#how-to-avoid-lousy-error-handling">How to avoid lousy Error Handling?</a><ol><li><a href="https://svenruppert.com/?p=1528#information-leakage-through-error-messages">Information Leakage through Error Messages</a></li><li><a href="https://svenruppert.com/?p=1528#denial-of-service-dos">Denial of Service (DoS)</a></li><li><a href="https://svenruppert.com/?p=1528#exception-handling-overhead">Exception Handling Overhead</a></li><li><a href="https://svenruppert.com/?p=1528#uncaught-exceptions">Uncaught Exceptions</a></li><li><a href="https://svenruppert.com/?p=1528#improper-use-of-checked-and-unchecked-exceptions">Improper Use of Checked and Unchecked Exceptions</a></li><li><a href="https://svenruppert.com/?p=1528#security-decisions-based-on-exception-handling">Security Decisions Based on Exception Handling</a></li><li><a href="https://svenruppert.com/?p=1528#error-handling-and-code-injection-risks">Error Handling and Code Injection Risks</a></li></ol></li></ol><p>Here are some critical aspects of error handling:</p><p><strong>Detection</strong> : Identifying situations that may lead to errors.</p><p><strong>Intervention</strong> : Implement strategies to handle the Error once it is detected. This may include logging the Error for further analysis, notifying users or administrators, and attempting recovery.</p><p><strong>Recovery</strong> : Trying to continue the program&rsquo;s execution by bypassing the Error or correcting the error condition.</p><p><strong>Graceful Degradation</strong> : If recovery is not possible, the system may continue operating with reduced functionality.</p><p><strong>Fail-Safe Operations</strong> : Ensuring that the system fails in a way that does not cause harm or excessive inconvenience to the user.</p><p>Programming languages and environments typically provide mechanisms to handle errors, such as Java, Python, or C++ exceptions. Handling these exceptions allows developers to write more robust and reliable software.</p><h2 id="how-is-error-handling-done-in-java">How is error handling done in Java?</h2><p>Error handling in Java is primarily accomplished through the use of exceptions. Exceptions are events that can alter the normal flow of program execution when an error or other unusual condition occurs. Java provides a robust and structured way to catch and handle these exceptions, making it possible to manage errors effectively. Here&rsquo;s how error handling is typically done in Java:</p><h3 id="types-of-exceptions">Types of Exceptions</h3><p>Java categorises exceptions into two main types:</p><p><strong>Checked Exceptions</strong> : These exceptions must be explicitly caught or declared in the method where they might be thrown. They are checked at compile time, meaning the compiler requires the programmer to handle them, typically through<code>try-catch</code> blocks or by declaring the Exception in the method signature with<code>throws</code>.</p><p><strong>Unchecked Exceptions</strong> : These include runtime exceptions and errors and do not need to be explicitly handled. Runtime exceptions typically result from programming errors such as logic mistakes, improper use of an API, etc. Errors are used by the Java runtime to indicate serious problems that a reasonable application should not try to catch, like<code>OutOfMemoryError</code>.</p><h3 id="exception-handling-mechanisms">Exception Handling Mechanisms</h3><h4 id="try-catch-block">Try-Catch Block</h4><p>The primary exception mechanism is the<code>try-catch</code> block. Here&rsquo;s how it works:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//code that might throw an exception</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">ExceptionType</span><span class="w"/><span class="n">name</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//code that handles the Exception</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/><span class="k">finally</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//code that executes after try-catch, regardless of whether an exception was thrown</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><code>**try**</code>: The block that contains the code which might throw an exception.</p><p><code>**catch**</code>: This block catches the Exception thrown by the<code>try</code> block. Multiple catch blocks can handle different exceptions.</p><p><code>**finally**</code>: This block is optional and executes after the try-and-catch blocks. It executes regardless of whether an exception was thrown or caught. It&rsquo;s typically used for cleanup activities (like closing file streams).</p><h4 id="throws-keyword">Throws Keyword</h4><p>If a method is capable of causing an exception that it does not handle, it must declare this Exception with the<code>throws</code> keyword in its method signature:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">myMethod</span><span class="p">()</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c1">//code that might throw an IOException</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h4 id="throw-keyword">Throw Keyword</h4><p>Java also allows you to throw an exception explicitly using the<code>throw</code> keyword. This is often used to throw a custom exception or re-throw a caught exception after some specific handling:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="o">**</span><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Exception</span><span class="p">(</span><span class="s">"This is an error message"</span><span class="p">);</span><span class="o">**</span></span></span></code></pre></div></div><h3 id="custom-exceptions">Custom Exceptions</h3><p>You can create your exception types by extending the<code>Exception</code> class (for checked exceptions) or the<code>RuntimeException</code> class (for unchecked exceptions). This is useful for creating application-specific exceptions that can carry more contextual information about the Error:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MyCustomException</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="kd">public</span><span class="w"/><span class="nf">MyCustomException</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">message</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">super</span><span class="p">(</span><span class="n">message</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="best-practices">Best Practices</h3><p>- Catch the most specific Exception first before the more general ones.</p><p>- Avoid catching<code>Throwable</code>,<code>Exception</code>, or<code>RuntimeException</code> unless necessary, as it can hide bugs.</p><p>- Always clean up after handling an exception, either in a<code>finally</code> block or using try-with-resources for AutoCloseable resources.</p><p>By following these structured approaches, Java programs can handle errors gracefully, making applications more robust, secure, and user-friendly.</p><h2 id="why-is-error-handling-necessary-for-security">Why is Error handling necessary for security?</h2><p>Error handling is critically important for security for several reasons, as it directly impacts the robustness and resilience of a system. Here are some key ways in which effective error handling enhances security:</p><p><strong>Preventing Information Disclosure</strong> : Poor error handling can unintentionally leak sensitive information. For example, a detailed error message could reveal system details, such as file paths, database schema, or software versions, which attackers could exploit. Proper error handling should mask or generalise error messages shown to users while logging detailed information securely for debugging purposes by administrators.</p><p><strong>Avoiding Denial of Service (DoS)</strong> : Software that does not gracefully handle errors can crash or become unresponsive when faced with unexpected conditions, making it vulnerable to DoS attacks. Proper error handling ensures that the application can continue to operate or fail safely without affecting overall availability.</p><p><strong>Mitigating Injection Flaws</strong> : Applications that fail to handle errors properly are often vulnerable to injection attacks, such as SQL injection, where attackers exploit error messages to glean insights about the database structure. By securely handling errors, applications can prevent such attacks from being successful.</p><p><strong>Ensuring Data Integrity</strong> : Effective error handling can help maintain data integrity by checking for errors during data processing and preventing corrupt or partial data from being saved to the database. This is crucial in avoiding scenarios where erroneous data could lead to security vulnerabilities or functional errors.</p><p><strong>Managing Untrusted Data and Inputs</strong> : By handling errors properly, applications can safely deal with untrusted inputs that might otherwise cause failures or security breaches. For instance, if an application tries to process a considerable file and fails due to resource limitations, properly handling this condition prevents it from being used as a vector for attacks.</p><p><strong>Upholding Compliance and Trust</strong> : Many regulatory standards require robust error handling as part of compliance mandates. Failing to implement proper error handling can lead to compliance failures and legal liabilities. Moreover, a well-handled error scenario can maintain user trust, whereas repeated failures or uninformative error messages can erode it.</p><p>Overall, robust error handling is essential in designing secure systems. It prevents specific security vulnerabilities and contributes to the software&rsquo;s overall resilience and reliability.</p><h2 id="how-to-avoid-lousy-error-handling">How to avoid lousy Error Handling?</h2><p>When not appropriately implemented, error handling in Java can have several security implications. Here&rsquo;s a detailed look at potential issues and how to mitigate them:</p><h3 id="information-leakage-through-error-messages">Information Leakage through Error Messages</h3><p><strong>Problem</strong> : Detailed error messages can inadvertently reveal system valuable information to attackers. For example, stack traces exposed to users might include information about the software architecture, third-party libraries being used, database information, or specific methods in the code.</p><p><strong>Mitigation</strong> : Avoid sending detailed error messages to the client or end-user. Instead, log these details internally, where developers or system administrators can use them for debugging. Show users generic, user-friendly error messages that do not disclose sensitive information.</p><h3 id="denial-of-service-dos">Denial of Service (DoS)</h3><p><strong>Problem</strong> : If exceptions are not handled properly, an attacker might be able to exploit this by sending requests that they know will cause exceptions, potentially leading the application to crash or become unresponsive.</p><p><strong>Mitigation</strong> : Implement robust Exception handling that isolates error conditions and ensures the application can recover gracefully. Use techniques like input validation to prevent problematic data from causing unexpected behaviour.</p><h3 id="exception-handling-overhead">Exception Handling Overhead</h3><p><strong>Problem</strong> : Creating, throwing, and catching exceptions can be resource-intensive, especially if the exceptions involve stack trace generation. If an application generates many exceptions as part of normal operations, it can lead to performance issues and resource exhaustion.</p><p><strong>Mitigation</strong> : Optimise exceptions by avoiding them in standard flow control and using them only for exceptional conditions. Consider using return codes or validation methods to handle common, expected conditions.</p><h3 id="uncaught-exceptions">Uncaught Exceptions</h3><p><strong>Problem</strong> : The application can miss exceptions, causing the program to terminate unexpectedly, leading to potential denial of service and other stability issues.</p><p><strong>Mitigation</strong> : Ensure that all parts of the application are covered by adequate exception handling, and use global exception handlers as a last resort to catch and log any unhandled exceptions.</p><h3 id="improper-use-of-checked-and-unchecked-exceptions">Improper Use of Checked and Unchecked Exceptions</h3><p><strong>Problem</strong> : Misusing checked and unchecked exceptions can lead to poorly designed code that hides errors (unchecked) or burdens the developer with too many error-handling responsibilities (checked), potentially leading to ignored exceptions.</p><p><strong>Mitigation</strong> : Use checked exceptions for recoverable conditions and where you want to enforce the caller&rsquo;s handling. Use unchecked exceptions for programming errors that should not be handled programmatically.</p><h3 id="security-decisions-based-on-exception-handling">Security Decisions Based on Exception Handling</h3><p><strong>Problem</strong> : Relying on exception handling to make security decisions, such as authentication or validation, can lead to logic errors and potential security vulnerabilities.</p><p><strong>Mitigation</strong> : Design security controls independently of the exception-handling mechanism. Ensure that security decisions are made based on secure, well-established practices rather than the presence or absence of exceptions.</p><h3 id="error-handling-and-code-injection-risks">Error Handling and Code Injection Risks</h3><p><strong>Problem</strong> : Poorly handled errors can lead to injection vulnerabilities if the output from exceptions is not properly sanitised before being displayed to the user.</p><p><strong>Mitigation</strong> : Always sanitise any dynamic content displayed to users, including error messages, to prevent cross-site scripting (XSS) and other injection attacks.</p><p>Developers can significantly enhance Java applications&rsquo; security posture by understanding and addressing these security implications. Effective error handling prevents crashes, improves user experience, and strengthens the application&rsquo;s resistance to malicious attacks.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><media:content url="https://svenruppert.com/images/2024/05/B03F4373-5637-49F6-95D1-3F3ACD5E17E0.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/05/B03F4373-5637-49F6-95D1-3F3ACD5E17E0.jpg"/><enclosure url="https://svenruppert.com/images/2024/05/B03F4373-5637-49F6-95D1-3F3ACD5E17E0.jpg" type="image/jpeg" length="0"/></item><item><title>Decoding the Logs: Essential Insights for Effective Software Debugging</title><link>https://svenruppert.com/posts/decoding-the-logs-essential-insights-for-effective-software-debugging/</link><pubDate>Mon, 06 May 2024 21:12:46 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/decoding-the-logs-essential-insights-for-effective-software-debugging/</guid><description>Logging is essential to software development, recording information about the software&rsquo;s operation. This can help developers understand the system&rsquo;s behaviour, troubleshoot issues, and monitor the system in production. Here&rsquo;s a basic overview of logging in software development:</description><content:encoded>&lt;![CDATA[<p>Logging is essential to software development, recording information about the software&rsquo;s operation. This can help developers understand the system&rsquo;s behaviour, troubleshoot issues, and monitor the system in production. Here&rsquo;s a basic overview of logging in software development:</p><ol><li><a href="https://svenruppert.com/?p=1524#purpose-of-logging">Purpose of Logging</a><ol><li><a href="https://svenruppert.com/?p=1524#debugging-and-troubleshooting">Debugging and Troubleshooting:</a></li><li><a href="https://svenruppert.com/?p=1524#monitoring-system-health">Monitoring System Health:</a></li><li><a href="https://svenruppert.com/?p=1524#audit-trails">Audit Trails:</a></li><li><a href="https://svenruppert.com/?p=1524#security-analysis">Security Analysis:</a></li><li><a href="https://svenruppert.com/?p=1524#operational-intelligence">Operational Intelligence:</a></li><li><a href="https://svenruppert.com/?p=1524#regulatory-compliance">Regulatory Compliance:</a></li><li><a href="https://svenruppert.com/?p=1524#notification-of-important-events">Notification of Important Events:</a></li></ol></li><li><a href="https://svenruppert.com/?p=1524#what-to-log">What to Log</a><ol><li><a href="https://svenruppert.com/?p=1524#errors-and-exceptions">Errors and Exceptions</a></li><li><a href="https://svenruppert.com/?p=1524#system-events">System Events</a></li><li><a href="https://svenruppert.com/?p=1524#user-actions">User Actions</a></li><li><a href="https://svenruppert.com/?p=1524#performance-metrics">Performance Metrics</a></li><li><a href="https://svenruppert.com/?p=1524#security-events">Security Events</a></li><li><a href="https://svenruppert.com/?p=1524#warnings">Warnings</a></li></ol></li><li><a href="https://svenruppert.com/?p=1524#logging-levels">Logging Levels</a><ol><li><a href="https://svenruppert.com/?p=1524#debug">DEBUG</a></li><li><a href="https://svenruppert.com/?p=1524#info">INFO</a></li><li><a href="https://svenruppert.com/?p=1524#notice">NOTICE</a></li><li><a href="https://svenruppert.com/?p=1524#warning">WARNING</a></li><li><a href="https://svenruppert.com/?p=1524#error">ERROR</a></li><li><a href="https://svenruppert.com/?p=1524#critical">CRITICAL</a></li><li><a href="https://svenruppert.com/?p=1524#alert">ALERT</a></li><li><a href="https://svenruppert.com/?p=1524#emergency">EMERGENCY</a></li><li><a href="https://svenruppert.com/?p=1524#choosing-the-right-level">Choosing the Right Level</a></li></ol></li><li><a href="https://svenruppert.com/?p=1524#logging-best-practices">Logging Best Practices</a><ol><li><a href="https://svenruppert.com/?p=1524#use-established-logging-frameworks">Use Established Logging Frameworks</a></li><li><a href="https://svenruppert.com/?p=1524#implement-appropriate-log-levels">Implement Appropriate Log Levels</a></li><li><a href="https://svenruppert.com/?p=1524#ensure-logs-are-contextual-and-informative">Ensure Logs Are Contextual and Informative</a></li><li><a href="https://svenruppert.com/?p=1524#centralise-log-management">Centralise Log Management</a></li><li><a href="https://svenruppert.com/?p=1524#secure-and-protect-log-data">Secure and Protect Log Data</a></li><li><a href="https://svenruppert.com/?p=1524#regularly-monitor-and-analyse-logs">Regularly Monitor and Analyse Logs</a></li><li><a href="https://svenruppert.com/?p=1524#manage-log-volume">Manage Log Volume</a></li><li><a href="https://svenruppert.com/?p=1524#automate-log-analysis">Automate Log Analysis</a></li><li><a href="https://svenruppert.com/?p=1524#implement-log-retention-policies">Implement Log Retention Policies</a></li><li><a href="https://svenruppert.com/?p=1524#use-asynchronous-and-non-blocking-logging">Use Asynchronous and Non-blocking Logging</a></li><li><a href="https://svenruppert.com/?p=1524#standardise-logs-across-services">Standardise Logs Across Services</a></li></ol></li><li><a href="https://svenruppert.com/?p=1524#tools-for-log-management">Tools for Log Management</a><ol><li><a href="https://svenruppert.com/?p=1524#elk-stack">ELK Stack</a></li><li><a href="https://svenruppert.com/?p=1524#splunk">Splunk</a></li><li><a href="https://svenruppert.com/?p=1524#graylog">Graylog</a></li><li><a href="https://svenruppert.com/?p=1524#fluentd">Fluentd</a></li><li><a href="https://svenruppert.com/?p=1524#datadog">Datadog</a></li><li><a href="https://svenruppert.com/?p=1524#loggly">Loggly</a></li><li><a href="https://svenruppert.com/?p=1524#papertrail">Papertrail</a></li><li><a href="https://svenruppert.com/?p=1524#prometheus-and-grafana">Prometheus and Grafana</a></li><li><a href="https://svenruppert.com/?p=1524#choosing-the-right-tool">Choosing the Right Tool</a></li></ol></li><li><a href="https://svenruppert.com/?p=1524#common-pitfalls">Common Pitfalls</a><ol><li><a href="https://svenruppert.com/?p=1524#over-logging">Over-Logging</a></li><li><a href="https://svenruppert.com/?p=1524#under-logging">Under-Logging</a></li><li><a href="https://svenruppert.com/?p=1524#ignoring-logs">Ignoring Logs</a></li><li><a href="https://svenruppert.com/?p=1524#not-protecting-log-information">Not Protecting Log Information</a></li><li><a href="https://svenruppert.com/?p=1524#inconsistent-logging">Inconsistent Logging</a></li><li><a href="https://svenruppert.com/?p=1524#lack-of-context-in-logs">Lack of Context in Logs</a></li><li><a href="https://svenruppert.com/?p=1524#blocking-or-synchronous-logging">Blocking or Synchronous Logging</a></li><li><a href="https://svenruppert.com/?p=1524#poor-log-management-and-retention-practices">Poor Log Management and Retention Practices</a></li><li><a href="https://svenruppert.com/?p=1524#failing-to-plan-for-log-scalability">Failing to Plan for Log Scalability</a></li></ol></li></ol><h2 id="purpose-of-logging">Purpose of Logging</h2><p>The purpose of logging in software development is multifaceted, encompassing several vital aspects that collectively improve software systems&rsquo; operability, security, and maintainability. Here are the primary purposes of logging:</p><h3 id="debugging-and-troubleshooting">Debugging and Troubleshooting:</h3><p>Logging gives developers detailed insights into the application&rsquo;s behaviour, allowing them to trace the steps leading up to errors, exceptions, and other anomalous behaviours. This is invaluable for debugging and can significantly reduce the time needed to diagnose and fix issues.</p><h3 id="monitoring-system-health">Monitoring System Health:</h3><p>Logs can provide real-time information about the health of an application, including performance metrics such as response times, throughput, and resource utilisation. This helps proactively manage system performance and identify potential issues before they affect users.</p><h3 id="audit-trails">Audit Trails:</h3><p>Logging creates an audit trail in many applications, particularly those that handle financial transactions, sensitive data, or personal information. This helps track user actions and system changes, which is crucial for compliance with regulations and standards (such as GDPR, HIPAA, or SOX).</p><h3 id="security-analysis">Security Analysis:</h3><p>Logs can detect and alert potential security incidents. For instance, a high number of failed login attempts could indicate a brute-force attack. Logs also help in the forensic analysis after an incident, enabling the understanding of how the security breach occurred and the extent of the impact.</p><h3 id="operational-intelligence">Operational Intelligence:</h3><p>Analysing logs, businesses can gain insights into user behaviour, system usage patterns, and operational bottlenecks. This information can guide decisions on system improvements, user support, and new feature development.</p><h3 id="regulatory-compliance">Regulatory Compliance:</h3><p>Logging is not just an operational tool but a legal requirement for many industries. Logs must be maintained to demonstrate compliance with various regulatory frameworks and show that the system performs correctly and securely.</p><h3 id="notification-of-important-events">Notification of Important Events:</h3><p>Logging systems can be configured to send alerts when critical system events occur, such as system outages, significant performance degradation, or other critical issues that require immediate attention.</p><p>Logging is an essential discipline in software development that enhances visibility into applications, ensuring they run smoothly, securely, and in compliance with legal and operational requirements. It also helps developers and IT professionals effectively manage, diagnose, and optimise their software environments.</p><h2 id="what-to-log">What to Log</h2><p>Deciding what to log in a software system is crucial for ensuring you gather enough information to be helpful in debugging, monitoring, and compliance without overwhelming the system or the teams that need to parse through the logs. Here are key categories and specific elements you should consider logging:</p><h3 id="errors-and-exceptions">Errors and Exceptions</h3><p><strong>Critical Failures</strong> : Any errors that cause a part of your system to fail or potentially disrupt service.</p><p><strong>Exceptions</strong> : Catch and log exceptions with stack traces and context to understand why they occurred.</p><h3 id="system-events">System Events</h3><p><strong>Startups and Shutdowns</strong> : Record when your system or service starts and stops.</p><p><strong>Configuration Changes</strong> : Log any changes in a system configuration that might affect behaviour or performance.</p><p><strong>Scheduled Tasks</strong> : Record when scheduled tasks begin and end, especially if they&rsquo;re crucial to system functionality.</p><h3 id="user-actions">User Actions</h3><p><strong>Logins and Logouts</strong> : Tracking user sessions can help detect unauthorised access and understand user engagement.</p><p><strong>Important Transactions</strong> : Logging these activities is vital, especially in systems handling payments, orders, or sensitive operations.</p><p><strong>Access to Sensitive Data</strong> : Log in when users access sensitive information to comply with privacy laws and regulations.</p><h3 id="performance-metrics">Performance Metrics</h3><p><strong>Response Times</strong> : Log how long it takes to respond to user requests.</p><p><strong>System Utilisation</strong> : Include metrics on CPU, memory, disk, and network usage.</p><p><strong>Service Availability</strong> : Record any downtime or interruptions in service.</p><h3 id="security-events">Security Events</h3><p><strong>Failed Logins</strong> : An excessive number of failed login attempts can indicate a brute-force attack.</p><p><strong>Permission Changes</strong> : Track user permissions changes, especially for administrative access users.</p><p><strong>Security Breaches</strong> : Log any detected breaches or potential security threats.</p><h3 id="warnings">Warnings</h3><p><strong>Resource Limits</strong> : Warnings when resources (e.g., memory, disk space) run low.</p><p><strong>Deprecations</strong> : Log usage of deprecated APIs or features that will be removed in future.</p><h2 id="logging-levels">Logging Levels</h2><p>Log levels are a way to categorise entries in your logs based on their importance and the detail of information they provide. These levels help filter logs, like debugging, monitoring, and alerting. Here&rsquo;s a breakdown of expected log levels used in many logging systems, from the most verbose to the least:</p><h3 id="debug">DEBUG</h3><p><strong>Purpose</strong> : This is the most detailed level for total diagnostic output. It includes valuable information for developers during development and debugging to understand precisely what the system is doing.</p><p><strong>Use Case</strong> : You would enable DEBUG logging when trying to solve a tricky bug or when you need a detailed trace of how data flows through your system.</p><h3 id="info">INFO</h3><p><strong>Purpose</strong> : General information about the application&rsquo;s operation. These entries should be informative and relevant to users or system administrators monitoring the healthy functioning of the application.</p><p><strong>Use Case</strong> : Routine operations like user logins, SQL logs, and other operational milestones.</p><h3 id="notice">NOTICE</h3><p><strong>Purpose</strong> : Important runtime events that are not necessarily errors but may require attention or be significant in system auditing or analysis.</p><p><strong>Use Case</strong> : Deprecation warnings, minor configuration issues, or other messages that don&rsquo;t require immediate action but should be noted for potential future relevance.</p><h3 id="warning">WARNING</h3><p><strong>Purpose</strong> : Indicative of something unexpected or a potential problem in the future. However, the application can continue running.</p><p><strong>Use Case</strong> : Recoverable malfunctions, such as retrying operations, missing secondary data, or running with default values due to missing configuration.</p><h3 id="error">ERROR</h3><p><strong>Purpose</strong> : This indicator indicates issues that are of immediate concern, as they may hinder system operations or result in a partial application failure.</p><p><strong>Use Case</strong> : Runtime errors, inability to access a necessary resource, exceptions that are handled but disrupt regular operation.</p><h3 id="critical">CRITICAL</h3><p><strong>Purpose</strong> : Very serious issues that might cause the application to terminate or affect essential functionality.</p><p><strong>Use Case</strong> : Critical conditions include data loss scenarios, out-of-memory, or data corruption.</p><h3 id="alert">ALERT</h3><p><strong>Purpose</strong> : A step above CRITICAL, requiring immediate attention. This typically involves issues that need to be fixed immediately to prevent stoppage or significant damage to operations.</p><p><strong>Use Case</strong> : Breaches in security, system components going down, loss of connectivity.</p><h3 id="emergency">EMERGENCY</h3><p><strong>Purpose</strong> : This is the highest level, used when the system is unusable or major parts of the system are non-functional.</p><p><strong>Use Case</strong> : Complete system outage, catastrophic failure, or anything that requires immediate and urgent attention to prevent harm or significant disruption.</p><h3 id="choosing-the-right-level">Choosing the Right Level</h3><p>When choosing which level to log in, consider the following:</p><ul><li><p><strong>The audience for the logs</strong> : Developers, system administrators, end-users, or auditors.</p></li><li><p><strong>The environment</strong> : Development, testing, staging, or production.</p></li><li><p><strong>The severity of the event</strong> : The impact on the system&rsquo;s functionality and the user&rsquo;s experience.</p></li></ul><p>Proper use of log levels allows you to control the verbosity of logging output, making the logs both manageable and meaningful. This control is significant in production environments where excessive logging can lead to performance degradation and increased log storage and management costs.</p><h2 id="logging-best-practices">Logging Best Practices</h2><p>Adopting best practices in logging is crucial for creating a robust and effective logging strategy. Here are several fundamental guidelines to help ensure your logging system is both efficient and valuable:</p><h3 id="use-established-logging-frameworks">Use Established Logging Frameworks</h3><p><strong>Why</strong> : Leverage well-maintained and community-tested libraries to handle routine logging tasks. Examples include Log4j for Java, Serilog for .NET, Winston for Node.js, and Python&rsquo;s built-in<code>logging</code> module.</p><p><strong>Benefit</strong> : These frameworks support features like log rotation, different logging levels, and integration with various logging backends.</p><h3 id="implement-appropriate-log-levels">Implement Appropriate Log Levels</h3><p><strong>Why</strong> : Differentiate log messages according to severity levels (DEBUG, INFO, WARNING, ERROR, etc.).</p><p><strong>Benefit</strong> : Allows fine-grained control over which log entries are output based on the current runtime environment, helping to reduce noise in production logs and focus on relevant data.</p><h3 id="ensure-logs-are-contextual-and-informative">Ensure Logs Are Contextual and Informative</h3><p><strong>Why</strong> : Log messages should include enough context to be understood independently. Context can consist of timestamps, user IDs, session IDs, and other relevant details.</p><p><strong>Benefit</strong> : Makes troubleshooting easier by clarifying when and where events occur, especially in distributed systems.</p><h3 id="centralise-log-management">Centralise Log Management</h3><p><strong>Why</strong> : In distributed systems, consolidate logs from multiple sources into a central log management solution.</p><p><strong>Benefit</strong> : Simplifies monitoring and analysis, enabling more effective incident detection and response. Tools like ELK Stack, Splunk, and Graylog are popular choices.</p><h3 id="secure-and-protect-log-data">Secure and Protect Log Data</h3><p><strong>Why</strong> : Logs often contain sensitive information which must be protected.</p><p><strong>Benefit</strong> : Prevents sensitive data exposure and complies with data protection regulations (like GDPR).</p><h3 id="regularly-monitor-and-analyse-logs">Regularly Monitor and Analyse Logs</h3><p><strong>Why</strong> : Active log monitoring helps identify and respond to issues promptly.</p><p><strong>Benefit</strong> : Reduces system downtime and can alert to emerging issues before they become critical.</p><h3 id="manage-log-volume">Manage Log Volume</h3><p><strong>Why</strong> : Avoid logging too much unnecessary information.</p><p><strong>Benefit</strong> : It helps manage log sizes, reduces storage costs, and improves log readability.</p><h3 id="automate-log-analysis">Automate Log Analysis</h3><p><strong>Why</strong> : Use tools and scripts to analyse logs for patterns and anomalies automatically.</p><p><strong>Benefit</strong> : Enhances the ability to quickly identify issues without manually sifting through logs, especially in large-scale systems.</p><h3 id="implement-log-retention-policies">Implement Log Retention Policies</h3><p><strong>Why</strong> : Define how long logs should be retained based on the type of data and compliance requirements.</p><p><strong>Benefit</strong> : It ensures that logs are available for sufficient time for audits and analysis while avoiding unnecessary storage costs.</p><h3 id="use-asynchronous-and-non-blocking-logging">Use Asynchronous and Non-blocking Logging</h3><p><strong>Why</strong> : Prevent logging operations from impacting application performance.</p><p><strong>Benefit</strong> : Minimises the performance overhead of logging activities on the main application processes.</p><h3 id="standardise-logs-across-services">Standardise Logs Across Services</h3><p><strong>Why</strong> : Use a consistent format across different services and parts of your application.</p><p><strong>Benefit</strong> : It simplifies the analysis and correlation of logs from different sources, which is particularly valuable in microservices architectures.</p><p>By integrating these best practices into your development and operational processes, you can maximise the benefits of logging, making it a powerful tool for maintaining and improving your software systems&rsquo; performance, reliability, and security.</p><h2 id="tools-for-log-management">Tools for Log Management</h2><p>Effective log management is critical for maintaining system performance, ensuring security, and troubleshooting issues across software applications. Here are some of the most popular tools and platforms that help in aggregating, analysing, and managing logs:</p><h3 id="elk-stack">ELK Stack</h3><p><strong>Components</strong> : Elasticsearch, Logstash, and Kibana.</p><p><strong>Use Case</strong> : ELK Stack is one of the most popular open-source choices for log management. Elasticsearch acts as a search and analytics engine, Logstash is used for log ingestion and processing, and Kibana is used for visualisation and querying.</p><p><strong>Benefits</strong> : Highly customisable and scalable, capable of handling massive volumes of data.</p><h3 id="splunk">Splunk</h3><p><strong>Use Case</strong> : Splunk is a powerful commercial solution with a web-based interface that provides extensive capabilities for searching, monitoring, and analysing machine-generated data.</p><p><strong>Benefits</strong> : Known for its advanced analytics features and extensive out-of-the-box functionalities that can handle complex queries across large datasets.</p><h3 id="graylog">Graylog</h3><p><strong>Use Case</strong> : Graylog is an open-source log management tool that offers centralised log management, efficient analysis, and a user-friendly dashboard.</p><p><strong>Benefits</strong> : It&rsquo;s known for its simplicity and efficiency in storing, searching, and analysing large amounts of data.</p><h3 id="fluentd">Fluentd</h3><p><strong>Use Case</strong> : Fluentd is an open-source data collector for unified logging layers, which allows you to unify data collection and consumption for better use and understanding of data.</p><p><strong>Benefits</strong> : Fluentd is particularly noted for its flexibility and a wide array of plugins that integrate with many data sources and output formats.</p><h3 id="datadog">Datadog</h3><p><strong>Use Case</strong> : Datadog provides cloud-scale monitoring that includes the ability to collect, search, and analyse log data, as well as infrastructure and application performance monitoring.</p><p><strong>Benefits</strong> : It offers real-time logs, sophisticated alerting, and seamless integration with various cloud services.</p><h3 id="loggly">Loggly</h3><p><strong>Use Case</strong> : Loggly provides cloud-based log management services that can analyse large volumes of data and extract useful information, typically geared towards enterprise-level users.</p><p><strong>Benefits</strong> : Loggly is easy to set up and integrates well with existing applications, providing robust search capabilities and interactive visualisations.</p><h3 id="papertrail">Papertrail</h3><p><strong>Use Case</strong> : Papertrail offers cloud-based log management that focuses on simplicity and fast setup, providing instant log visibility and analysis.</p><p><strong>Benefits</strong> : Its simplicity makes it ideal for smaller applications or teams needing quick setup without extensive configuration.</p><h3 id="prometheus-and-grafana">Prometheus and Grafana</h3><p><strong>Use Case</strong> : While Prometheus is primarily used for monitoring and alerting, it can be combined with Grafana for log visualisation. This combo is often used for monitoring and visually analysing metrics.</p><p><strong>Benefits</strong> : Open-source, decisive for visualising trends and patterns, and highly extensible with Grafana&rsquo;s advanced dashboard capabilities.</p><h3 id="choosing-the-right-tool">Choosing the Right Tool</h3><p>When selecting a log management tool, consider factors like:</p><p><strong>Volume of Data</strong> : The amount of log data you generate.</p><p><strong>The complexity of the Environment</strong> : Whether you are managing logs from a single application or distributed systems.</p><p><strong>Budget</strong> : Open-source vs. commercial solutions.</p><p><strong>Integration Needs</strong> : Compatibility with existing tools and platforms in your ecosystem.</p><p><strong>Compliance Requirements</strong> : Certain industries might need specific features to comply with legal standards.</p><p>Each tool has strengths and is suitable for different organisational needs and sizes. The right choice will depend on your specific requirements, including scalability, ease of use, budget, and the specific features you need.</p><h2 id="common-pitfalls">Common Pitfalls</h2><p>Logging is a powerful tool in software development, but it can also lead to issues if not appropriately managed. Here are some common pitfalls associated with logging and how to avoid them:</p><h3 id="over-logging">Over-Logging</h3><p><strong>Problem</strong> : Logging too much information can clutter log files, make them challenging to manage, and lead to performance degradation.</p><p><strong>Solution</strong> : Use appropriate log levels to control the verbosity of the logs. Log only what is necessary for troubleshooting and operational monitoring. Regularly review and adjust what is being logged based on current needs.</p><h3 id="under-logging">Under-Logging</h3><p><strong>Problem</strong> : Insufficient logging can leave you without enough information to diagnose issues, especially in production environments.</p><p><strong>Solution</strong> : Ensure critical paths and user transactions are well-logged. Log errors effectively, including error handling and catching exceptions. Use structured logging to enhance the quality and usefulness of the logs.</p><h3 id="ignoring-logs">Ignoring Logs</h3><p><strong>Problem</strong> : Not regularly reviewing logs can lead to missed opportunities to detect or fix issues early.</p><p><strong>Solution</strong> : Implement log monitoring and alerting tools to watch for unusual activity or errors actively. Establish routines for checking logs, especially after deployments or changes.</p><h3 id="not-protecting-log-information">Not Protecting Log Information</h3><p><strong>Problem</strong> : Logs can contain sensitive information, which, if exposed, can lead to security breaches.</p><p><strong>Solution</strong> : Secure log files by restricting access and using encryption where necessary. Be mindful of what is logged, especially avoiding logging sensitive user data like passwords or personal information.</p><h3 id="inconsistent-logging">Inconsistent Logging</h3><p><strong>Problem</strong> : Inconsistent log formats across different parts of an application can make it difficult to correlate events when analysing logs.</p><p><strong>Solution</strong> : Standardise log formats across the application. Use structured logging formats like JSON to make logs more uniform and more straightforward to analyse.</p><h3 id="lack-of-context-in-logs">Lack of Context in Logs</h3><p><strong>Problem</strong> : In complex or distributed systems, logs with sufficient context can be easier to interpret.</p><p><strong>Solution</strong> : Logs should include contextual information such as timestamps, user IDs, session IDs, and request IDs to clarify the events being logged.</p><h3 id="blocking-or-synchronous-logging">Blocking or Synchronous Logging</h3><p><strong>Problem</strong> : Synchronous logging can negatively impact application performance, causing delays in user-facing operations.</p><p><strong>Solution</strong> : Use asynchronous logging mechanisms to minimise the impact on application performance and ensure logging does not block critical application workflows.</p><h3 id="poor-log-management-and-retention-practices">Poor Log Management and Retention Practices</h3><p><strong>Problem</strong> : Inadequate log rotation and retention policies can lead to manageable log files and increased storage costs.</p><p><strong>Solution</strong> : Implement log rotation policies and configure appropriate log retention durations based on business needs and compliance requirements.</p><h3 id="failing-to-plan-for-log-scalability">Failing to Plan for Log Scalability</h3><p><strong>Problem</strong> : As systems grow, the volume of logs can increase dramatically, leading to scalability issues.</p><p><strong>Solution</strong> : Plan for log scalability from the start. Consider how logs will be handled, stored, and analysed as data volumes grow.</p><p>By being aware of these common pitfalls and actively working to avoid them, you can ensure that your logging practices enhance rather than hinder your application&rsquo;s development and operation. Effective logging practices lead to more maintainable, reliable, and secure software systems.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><media:content url="https://svenruppert.com/images/2024/05/D3CE80C8-FAB3-400F-A86B-EED05C7C1F57.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/05/D3CE80C8-FAB3-400F-A86B-EED05C7C1F57.jpg"/><enclosure url="https://svenruppert.com/images/2024/05/D3CE80C8-FAB3-400F-A86B-EED05C7C1F57.jpg" type="image/jpeg" length="0"/></item><item><title>Secure Coding Practices - Access Control</title><link>https://svenruppert.com/posts/secure-coding-practices-access-control/</link><pubDate>Fri, 03 May 2024 10:41:23 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/secure-coding-practices-access-control/</guid><description>Access control is a security measure that determines who can access resources or perform actions within a system. It involves defining and enforcing policies restricting unauthorised access while allowing authorised users to perform their intended tasks. Access control mechanisms are commonly used in various domains, including computer systems, buildings, and physical assets.</description><content:encoded>&lt;![CDATA[<p>Access control is a security measure that determines who can access resources or perform actions within a system. It involves defining and enforcing policies restricting unauthorised access while allowing authorised users to perform their intended tasks. Access control mechanisms are commonly used in various domains, including computer systems, buildings, and physical assets.</p><ol><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#what-are-the-different-access-control-ways-in-software">What are the different Access Control ways in software?</a><ol><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#discretionary-access-control-dac">Discretionary Access Control (DAC):</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#mandatory-access-control-mac">Mandatory Access Control (MAC):</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#role-based-access-control-rbac">Role-Based Access Control (RBAC):</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#attribute-based-access-control-abac">Attribute-Based Access Control (ABAC):</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#rule-based-access-control-rbac">Rule-Based Access Control (RBAC):</a></li></ol></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#what-are-the-secure-coding-practices-in-java-for-access-control">What are the secure coding practices in Java for Access Control?</a><ol><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#use-the-principle-of-least-privilege">Use the Principle of Least Privilege:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#implement-authentication-and-authorization">Implement Authentication and Authorization:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#avoid-hardcoding-sensitive-information">Avoid Hardcoding Sensitive Information:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#securely-manage-credentials">Securely Manage Credentials:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#secure-communication">Secure Communication:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#sanitize-inputs">Sanitize Inputs:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#implement-access-control-checks">Implement Access Control Checks:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#secure-error-handling">Secure Error Handling:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#regularly-update-dependencies">Regularly Update Dependencies:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#security-testing">Security Testing:</a></li></ol></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#what-are-the-pros-and-cons-of-access-control-implemented-in-java-s-static-or-dynamic-semantics">What are the pros and cons of Access Control implemented in Java&rsquo;s static or dynamic semantics?</a><ol><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#static-semantics">Static Semantics:</a><ol><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#pros">Pros:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#cons">Cons:</a></li></ol></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#dynamic-semantics">Dynamic Semantics:</a><ol><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#pros">Pros:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#cons">Cons:</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#what-are-common-design-patterns-in-java-for-implementing-access-control">What are common Design Patterns in Java for implementing Access Control?</a><ol><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#proxy-pattern">Proxy Pattern:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#decorator-pattern">Decorator Pattern:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#chain-of-responsibility-pattern">Chain of Responsibility Pattern:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#strategy-pattern">Strategy Pattern:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#facade-pattern">Facade Pattern:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#observer-pattern">Observer Pattern:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#singleton-pattern">Singleton Pattern:</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#command-pattern">Command Pattern:</a></li></ol></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#an-example-in-java-for-access-control-using-a-proxy">An example in Java for Access Control using a Proxy</a><ol><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#step-1-define-the-document-interface">Step 1: Define the Document Interface</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#step-2-implement-the-document-class">Step 2: Implement the Document Class</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#step-3-create-the-proxy-class">Step 3: Create the Proxy Class</a></li><li><a href="https://svenruppert.com/2024/03/18/secure-coding-practices-access-control/#step-4-demonstrate-the-proxy-in-action">Step 4: Demonstrate the Proxy in Action</a></li></ol></li></ol><p>In computer systems, access control typically involves the following components:</p><p><strong>Identification</strong> : Users must identify themselves to the system by providing a username or other unique identifier.</p><p><strong>Authentication</strong> : After identification, users must authenticate themselves by providing credentials such as passwords, biometric data, or security tokens.</p><p><strong>Authorisation</strong> : Once authenticated, users are granted access rights based on their identity and the permissions associated with that identity. Authorisation mechanisms define what resources or actions a user is allowed to access.</p><p><strong>Enforcement</strong> : The system enforces access control policies to ensure that only authorised users can access resources or perform specific actions. This may involve mechanisms such as encryption, access control lists (ACLs), or role-based access control (RBAC).</p><p>Access control can be implemented at various levels, including:</p><p><strong>Physical access control</strong> refers to restricting access to physical locations such as buildings, rooms, or data centres using mechanisms such as locks, keycards, or biometric scanners.</p><p><strong>Logical access control</strong> : Managing access to digital resources such as files, databases, or networks through software-based mechanisms such as user accounts, permissions, and encryption.</p><p>Access control is fundamental to information security and is essential for protecting sensitive data, preventing unauthorised activities, and ensuring compliance with regulations and organisational policies. It helps organisations manage risks associated with unauthorised access and maintain their resources&rsquo; confidentiality, integrity, and availability.</p><h2 id="what-are-the-different-access-control-ways-in-software">What are the different Access Control ways in software?</h2><p>In software systems, access control mechanisms are implemented to regulate and manage user access to resources. There are several approaches to access control in software:</p><h3 id="discretionary-access-control-dac">Discretionary Access Control (DAC):</h3><p>DAC allows the owner of a resource to control access to that resource. Owners can grant or revoke access permissions to other users or groups. Access control lists (ACLs) and file permissions are typical implementations of DAC. For example, Unix-like operating systems use file permissions (read, write, execute) to control access to files and directories.</p><h3 id="mandatory-access-control-mac">Mandatory Access Control (MAC):</h3><p>MAC enforces access control based on predefined security policies determined by system administrators or security policies. Users do not have control over access rights. Security labels or security clearances are assigned to resources and users. Access decisions are based on the resource&rsquo;s sensitivity and the user&rsquo;s clearance level. SELinux (Security-Enhanced Linux) and TrustedBSD are examples of MAC systems.</p><h3 id="role-based-access-control-rbac">Role-Based Access Control (RBAC):</h3><p>RBAC assigns permissions to roles, and users are assigned to specific roles. Users inherit the permissions associated with their roles. Roles represent job functions or responsibilities within an organisation, and access decisions are based on user roles. RBAC simplifies administration by centralising access control management and reducing the complexity of managing individual user permissions.</p><h3 id="attribute-based-access-control-abac">Attribute-Based Access Control (ABAC):</h3><p>ABAC evaluates access decisions based on user, resource, and environment attributes. Attributes can include:</p><ul><li>User attributes (e.g., role, department).</li><li>Resource attributes (e.g., sensitivity, type).</li><li>Environmental attributes (e.g., time, location).</li></ul><p>Policies define conditions that must be met for access to be granted based on attribute values. ABAC provides a flexible and dynamic access control model that can adapt to changing organisational requirements and contexts.</p><h3 id="rule-based-access-control-rbac">Rule-Based Access Control (RBAC):</h3><p>Rule-based access control defines access rules or policies that specify conditions under which access is granted or denied. Access decisions are based on evaluating these rules against the attributes of the user, resource, and environment. Rules can be defined using logical expressions, conditions, or patterns.</p><p>These access control models can be combined or extended to meet the specific security requirements of software systems. The choice of access control mechanism depends on factors such as the system&rsquo;s nature, the data&rsquo;s sensitivity, and organisational policies and regulations.</p><h2 id="what-are-the-secure-coding-practices-in-java-for-access-control">What are the secure coding practices in Java for Access Control?</h2><p>Secure coding practices in Java for access control help developers ensure that their applications enforce proper access control policies to protect sensitive data and prevent unauthorised access. Here are some best practices:</p><h3 id="use-the-principle-of-least-privilege">Use the Principle of Least Privilege:</h3><p>Grant users and components only the permissions they need to perform their tasks and no more. Limit access to sensitive resources to minimise the potential impact of a security breach.</p><h3 id="implement-authentication-and-authorization">Implement Authentication and Authorization:</h3><p>Authenticate users before granting access to protected resources. Use robust authentication mechanisms such as username/password, multi-factor authentication, or OAuth. Implement authorisation mechanisms such as role-based access control (RBAC) or attribute-based access control (ABAC) to enforce access control policies based on user roles, permissions, or attributes.</p><h3 id="avoid-hardcoding-sensitive-information">Avoid Hardcoding Sensitive Information:</h3><p>Avoid hardcoding sensitive information such as passwords, API keys, or cryptographic keys directly into the source code. Instead, store them securely in configuration files, environment variables, or secure key management systems.</p><h3 id="securely-manage-credentials">Securely Manage Credentials:</h3><p>Use secure storage mechanisms such as Java KeyStore (JKS) or Key Management Service (KMS) to store and manage sensitive credentials. Avoid storing plaintext passwords or sensitive information in memory longer than necessary. Use secure storage and encryption techniques when handling sensitive data.</p><h3 id="secure-communication">Secure Communication:</h3><p>Use secure communication protocols such as HTTPS (SSL/TLS) to encrypt data transmitted between clients and servers. Verify server certificates to prevent man-in-the-middle attacks and ensure the integrity and authenticity of communication.</p><h3 id="sanitize-inputs">Sanitize Inputs:</h3><p>Validate and sanitise user inputs to prevent injection attacks such as SQL injection, Cross-Site Scripting (XSS), or Command Injection. Input validation libraries or frameworks such as OWASP ESAPI or Hibernate Validator are used to validate and sanitise user inputs.</p><h3 id="implement-access-control-checks">Implement Access Control Checks:</h3><p>Perform access control checks at the entry points of sensitive operations or resources. Use access control mechanisms provided by Java frameworks or libraries such as Spring Security or Apache Shiro to enforce access control policies consistently across the application.</p><h3 id="secure-error-handling">Secure Error Handling:</h3><p>Implement proper error handling mechanisms to avoid exposing sensitive information in error messages. Use custom error messages or error logging mechanisms to provide informative error messages to developers while hiding sensitive details from end-users.</p><h3 id="regularly-update-dependencies">Regularly Update Dependencies:</h3><p>Keep your Java libraries and dependencies up-to-date to ensure that you are using the latest security patches and fixes. Monitor security advisories and vulnerability databases for known security issues in third-party libraries and promptly apply patches or updates.</p><h3 id="security-testing">Security Testing:</h3><p>Perform regular security testing, including penetration testing, code reviews, and security scans, to identify and remediate security vulnerabilities in your Java applications. Use automated security testing tools such as OWASP ZAP, SonarQube, or FindBugs to identify common security issues and vulnerabilities in your code.</p><p>By following these secure coding practices, Java developers can strengthen the access control mechanisms in their applications and reduce the risk of security breaches and unauthorised access to sensitive data.</p><h2 id="what-are-the-pros-and-cons-of-access-control-implemented-in-javas-static-or-dynamic-semantics">What are the pros and cons of Access Control implemented in Java&rsquo;s static or dynamic semantics?</h2><p>Access control can be implemented in both Java&rsquo;s static and dynamic semantics. Here are the pros and cons of each approach:</p><h3 id="static-semantics">Static Semantics:</h3><h4 id="pros">Pros:</h4><p><strong>Early Detection of Errors</strong> : Static access control mechanisms are enforced at compile-time, allowing for early detection of access control violations. This helps identify potential security vulnerabilities before the code is executed.</p><p><strong>Compile-Time Optimization</strong> : The compiler can optimise static access control checks, improving the application&rsquo;s performance by reducing runtime overhead.</p><p><strong>Enforcement of Policies</strong> : Static access control mechanisms enforce access control policies uniformly across the entire codebase, ensuring consistency and reducing the likelihood of human error.</p><h4 id="cons">Cons:</h4><p><strong>Limited Flexibility</strong> : Static access control mechanisms may lack the flexibility to adapt to runtime changes or dynamic conditions. This can be problematic when access control policies must be dynamically modified or customised based on runtime context.</p><p><strong>Limited Granularity</strong> : Static access control mechanisms may have limitations in providing fine-grained access control, especially in complex or dynamic systems where access privileges must be defined at a more granular level.</p><h3 id="dynamic-semantics">Dynamic Semantics:</h3><h4 id="pros-1">Pros:</h4><p><strong>Flexibility</strong> : Dynamic access control mechanisms can adapt to changing runtime conditions, allowing for more flexible access control policies that can be adjusted based on dynamic factors such as user roles, permissions, or environmental context.</p><p><strong>Fine-Grained Access Control</strong> : Dynamic access control mechanisms can provide finer-grained access control, allowing for more precise specification of access privileges based on specific runtime conditions or attributes.</p><h4 id="cons-1">Cons:</h4><p><strong>Runtime Overhead</strong> : Dynamic access control checks incur runtime overhead, as access control decisions are made during program execution. This can potentially impact the application&rsquo;s performance, especially in performance-sensitive systems.</p><p><strong>Late Detection of Errors</strong> : Dynamic access control mechanisms may only detect access control violations during runtime, which can delay the detection and resolution of security vulnerabilities. If access control policies are not adequately enforced, this increases the risk of security breaches.</p><p><strong>Complexity</strong> : Dynamic access control mechanisms can introduce complexity, especially in large or distributed systems, making managing and maintaining access control policies challenging.</p><p>In summary, the choice between static and dynamic access control semantics in Java depends on factors such as the specific requirements of the application, the level of flexibility needed, and the trade-offs between performance and security. While static access control provides early detection of errors and compile-time optimisation, dynamic access control offers flexibility and fine-grained control but may incur runtime overhead and complexity.</p><h2 id="what-are-common-design-patterns-in-java-for-implementing-access-control">What are common Design Patterns in Java for implementing Access Control?</h2><p>Several design patterns can be applied in Java to implement access control effectively. Here are some common design patterns used for access control:</p><h3 id="proxy-pattern">Proxy Pattern:</h3><p>The Proxy pattern allows for controlling access to objects by acting as intermediaries. Proxy objects can enforce access control rules before allowing the client to access the object. Proxies can restrict access to sensitive resources based on user permissions or roles in access control.</p><h3 id="decorator-pattern">Decorator Pattern:</h3><p>The Decorator pattern enables dynamic augmentation of object behaviour by wrapping them with additional functionality. In access control, decorators can add security checks or authorisation logic around methods that access sensitive resources.</p><h3 id="chain-of-responsibility-pattern">Chain of Responsibility Pattern:</h3><p>The Chain of Responsibility pattern creates a chain of handler objects, each responsible for processing a request. The request is passed along the chain until it is handled. In access control, a chain of responsibility can be used to enforce access control checks at various application levels, with each handler responsible for verifying specific access rights.</p><h3 id="strategy-pattern">Strategy Pattern:</h3><p>The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Clients can choose the appropriate algorithm at runtime. In access control, the strategy pattern can be used to implement different access control strategies (e.g., RBAC, ABAC) and dynamically allow the application to switch between them.</p><h3 id="facade-pattern">Facade Pattern:</h3><p>The Facade pattern provides a unified interface to a set of interfaces in a subsystem, making it easier to use. In access control, a facade can hide the complexity of access control mechanisms by providing a simplified interface for clients to interact with while internally handling the access control logic.</p><h3 id="observer-pattern">Observer Pattern:</h3><p>The Observer pattern defines a one-to-many dependency between objects, where the state changes in one object are propagated to its dependents. In access control, observers can notify interested parties (e.g., audit logs, monitoring systems) about access control events, such as access granted or denied.</p><h3 id="singleton-pattern">Singleton Pattern:</h3><p>The Singleton pattern ensures that a class has only one instance and provides a global access point to that instance. In access control, a singleton can be used to manage a centralised access control policy or configuration, ensuring consistency across the application.</p><h3 id="command-pattern">Command Pattern:</h3><p>The Command pattern encapsulates a request as an object, thereby allowing for parameterisation of clients with queues, requests, and operations. In access control, commands can represent access control requests, encapsulating the necessary information (e.g., user, resource, action) to perform access control checks.</p><p>By applying these design patterns, developers can design flexible, maintainable, and extensible access control mechanisms in Java applications, ensuring that access to sensitive resources is adequately managed and enforced.</p><h2 id="an-example-in-java-for-access-control-using-a-proxy">An example in Java for Access Control using a Proxy</h2><p>To illustrate access control using a proxy in Java, let&rsquo;s create a simple example where we have a<code>Document</code> class that users can read or write. We will then use a<code>DocumentProxy</code> class to control access to the<code>Document</code> based on the user&rsquo;s role (e.g., &ldquo;admin&rdquo; can read and write, while &ldquo;user&rdquo; can only read).</p><h3 id="step-1-define-the-document-interface">Step 1: Define the Document Interface</h3><p>This interface declares the methods for interacting with a document.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">Document</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">displayDocument</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">modifyDocument</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">content</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="step-2-implement-the-document-class">Step 2: Implement the Document Class</h3><p>This class implements the<code>Document</code> interface. It represents a real document that can be modified and displayed.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">RealDocument</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Document</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">content</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">RealDocument</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">content</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">content</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">content</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">displayDocument</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Displaying Document: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">content</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">modifyDocument</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">content</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">content</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">content</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Document Modified"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="step-3-create-the-proxy-class">Step 3: Create the Proxy Class</h3><p>The<code>DocumentProxy</code> class controls access to the<code>RealDocument</code> based on the user&rsquo;s role.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">DocumentProxy</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Document</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">RealDocument</span><span class="w"/><span class="n">realDocument</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">userRole</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">DocumentProxy</span><span class="p">(</span><span class="n">RealDocument</span><span class="w"/><span class="n">realDocument</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">userRole</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">realDocument</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">realDocument</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">userRole</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">userRole</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">displayDocument</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">realDocument</span><span class="p">.</span><span class="na">displayDocument</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">modifyDocument</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">content</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="s">"admin"</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">userRole</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">realDocument</span><span class="p">.</span><span class="na">modifyDocument</span><span class="p">(</span><span class="n">content</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Access Denied: You do not have permission to modify the document."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="step-4-demonstrate-the-proxy-in-action">Step 4: Demonstrate the Proxy in Action</h3><p>Finally, let&rsquo;s create a simple demo to illustrate how the proxy controls access based on the user role.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ProxyDemo</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">RealDocument</span><span class="w"/><span class="n">document</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RealDocument</span><span class="p">(</span><span class="s">"Original Content"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//user trying to modify the document</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">DocumentProxy</span><span class="w"/><span class="n">userProxy</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DocumentProxy</span><span class="p">(</span><span class="n">document</span><span class="p">,</span><span class="w"/><span class="s">"user"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">userProxy</span><span class="p">.</span><span class="na">displayDocument</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">userProxy</span><span class="p">.</span><span class="na">modifyDocument</span><span class="p">(</span><span class="s">"New User Content"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Admin trying to modify the document</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">DocumentProxy</span><span class="w"/><span class="n">adminProxy</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DocumentProxy</span><span class="p">(</span><span class="n">document</span><span class="p">,</span><span class="w"/><span class="s">"admin"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">adminProxy</span><span class="p">.</span><span class="na">displayDocument</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">adminProxy</span><span class="p">.</span><span class="na">modifyDocument</span><span class="p">(</span><span class="s">"New Admin Content"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">adminProxy</span><span class="p">.</span><span class="na">displayDocument</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, when a &ldquo;<strong>user</strong> " tries to modify the document through the &ldquo;<strong>DocumentProxy</strong> &ldquo;, access is denied. However, when an &ldquo;<strong>admin</strong> " attempts to change it, the operation is successful. This demonstrates how a proxy can be used to control access to certain functionalities based on user roles.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><media:content url="https://svenruppert.com/images/2024/03/DALL%C2%B7E-2024-03-13-10.39.30-A-detailed-illustration-that-captures-the-concept-of-access-control-using-a-proxy-in-Java-programming.-The-scene-is-set-in-an-office-environment-feat.webp" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/03/DALL%C2%B7E-2024-03-13-10.39.30-A-detailed-illustration-that-captures-the-concept-of-access-control-using-a-proxy-in-Java-programming.-The-scene-is-set-in-an-office-environment-feat.webp"/><enclosure url="https://svenruppert.com/images/2024/03/DALL%C2%B7E-2024-03-13-10.39.30-A-detailed-illustration-that-captures-the-concept-of-access-control-using-a-proxy-in-Java-programming.-The-scene-is-set-in-an-office-environment-feat.webp" type="image/jpeg" length="0"/></item><item><title>The Hidden Dangers of Bidirectional Characters</title><link>https://svenruppert.com/posts/the-hidden-dangers-of-bidirectional-characters/</link><pubDate>Fri, 19 Apr 2024 10:12:58 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/the-hidden-dangers-of-bidirectional-characters/</guid><description>Discover the hidden dangers of bidirectional control characters! We dive deep into how these essential text-rendering tools can be exploited to manipulate digital environments. Learn about their security risks, from filename spoofing to deceptive URLs, and uncover the crucial strategies to safeguard against these subtle yet potent threats. Understand how to protect your systems in a multilingual world. Join to ensure your digital security is not left to chance!</description><content:encoded>&lt;![CDATA[<p>Discover the hidden dangers of bidirectional control characters! We dive deep into how these essential text-rendering tools can be exploited to manipulate digital environments. Learn about their security risks, from filename spoofing to deceptive URLs, and uncover the crucial strategies to safeguard against these subtle yet potent threats. Understand how to protect your systems in a multilingual world. Join to ensure your digital security is not left to chance!</p><ol><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#key-bidirectional-control-characters">Key Bidirectional Control Characters</a><ol><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#left-to-right-mark-lrm-u-200e">Left-to-Right Mark (LRM) - U+200E:</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#right-to-left-mark-rlm-u-200f">Right-to-Left Mark (RLM) - U+200F:</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#left-to-right-embedding-lre-u-202a">Left-to-Right Embedding (LRE) - U+202A:</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#right-to-left-embedding-rle-u-202b">Right-to-Left Embedding (RLE) - U+202B:</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#pop-directional-formatting-pdf-u-202c">Pop Directional Formatting (PDF) - U+202C:</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#left-to-right-override-lro-u-202d">Left-to-Right Override (LRO) - U+202D:</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#right-to-left-override-rlo-u-202e">Right-to-Left Override (RLO) - U+202E:</a></li></ol></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#uses-and-applications">Uses and Applications</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#some-demos">Some Demos</a><ol><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#java-demo-right-to-left-override-rlo-attack">Java Demo - Right-to-Left Override (RLO) Attack</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#explanation">Explanation</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#java-demo-right-to-left-mark-rlm-attack">Java Demo - Right-to-Left Mark (RLM) Attack</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#explanation">Explanation</a></li></ol></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#but-why-is-this-a-security-issue">But why is this a security issue?</a><ol><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#file-name-spoofing">File Name Spoofing</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#phishing-attacks">Phishing Attacks</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#code-obfuscation">Code Obfuscation</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#misleading-data-and-database-entries">Misleading Data and Database Entries</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#user-interface-deception">User Interface Deception</a></li></ol></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#addressing-the-security-risks">Addressing the Security Risks</a><ol><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#input-validation-and-sanitization">Input Validation and Sanitization</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#secure-default-configurations">Secure Default Configurations</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#user-and-administrator-education">User and Administrator Education</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#enhanced-monitoring-and-logging">Enhanced Monitoring and Logging</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#security-policies-and-procedures">Security Policies and Procedures</a></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#technological-solutions">Technological Solutions</a></li></ol></li><li><a href="https://svenruppert.com/2024/04/19/the-hidden-dangers-of-bidirectional-characters/#conclusion">Conclusion:</a></li></ol><h2 id="key-bidirectional-control-characters">Key Bidirectional Control Characters</h2><p>Bidirectional control characters (often abbreviated as bidi control characters) are special characters used in text encoding to manage the direction of text flow. This is crucial for languages read right-to-left (RTL), like Arabic and Hebrew, when mixed with left-to-right (LTR) languages like English. These characters help to ensure that the text is displayed in the correct order, regardless of the directionality of its parts.</p><p>Here are some of the common bidi control characters defined in the Unicode standard:</p><h3 id="left-to-right-mark-lrm---u200e">Left-to-Right Mark (LRM) - U+200E:</h3><p>They are used to set the direction of the text from left to right. It is particularly useful when embedding a small piece of LTR text within a larger segment of RTL text.</p><h3 id="right-to-left-mark-rlm---u200f">Right-to-Left Mark (RLM) - U+200F:</h3><p>This setting sets the direction of the text to right-to-left. It is used when embedding a small RTL text within a larger segment of LTR text.</p><h3 id="left-to-right-embedding-lre---u202a">Left-to-Right Embedding (LRE) - U+202A:</h3><p>They are used to start a segment of LTR text within an RTL environment. This embedding level pushes onto the directional status stack.</p><h3 id="right-to-left-embedding-rle---u202b">Right-to-Left Embedding (RLE) - U+202B:</h3><p>They are used to start a segment of RTL text within an LTR environment.</p><h3 id="pop-directional-formatting-pdf---u202c">Pop Directional Formatting (PDF) - U+202C:</h3><p>They are used to end a segment of embedded text, popping the last direction from the stack and returning to the previous directional context.</p><h3 id="left-to-right-override-lro---u202d">Left-to-Right Override (LRO) - U+202D:</h3><p>It forces the text within its scope to be treated as left-to-right text, regardless of its directionality. This is useful for reordering sequences of characters.</p><h3 id="right-to-left-override-rlo---u202e">Right-to-Left Override (RLO) - U+202E:</h3><p>Forces the text within its scope to be treated as right-to-left text, even if it is typically LTR. This can be used to display text backwards, which might be used for effect or in specific contexts.</p><h2 id="uses-and-applications">Uses and Applications</h2><p>Bidirectional control characters are essential for the following:</p><p><strong>Multilingual Documents</strong> : Ensuring coherent text flow when documents contain multiple languages with different reading directions.</p><p><strong>User Interfaces</strong> : Proper text rendering in software that supports multiple languages.</p><p><strong>Data Files</strong> : Manage data display in multiple languages with different directionalities.</p><h2 id="some-demos">Some Demos</h2><p>Bidirectional control characters can pose security risks. They can be used to obscure the true intent of a code or text, leading to what is known as a &ldquo;bidirectional text attack.&rdquo; For instance, filenames could appear to end with a harmless extension like &ldquo;.txt&rdquo; when they end with a dangerous one like &ldquo;.exe&rdquo; reversed by bidi characters. As a result, users might need to be more informed about the nature of the files they interact with.</p><p>Security-aware text editors and systems often have measures to detect and appropriately display or alert users about the presence of bidirectional control characters to mitigate potential security risks.</p><p>Here&rsquo;s a simple Java demo that illustrates how bidirectional control characters can be used to create misleading filenames. This can demonstrate the potential danger, particularly in environments where filenames are manipulated or displayed based on user input.</p><h3 id="java-demo---right-to-left-override-rlo-attack">Java Demo - Right-to-Left Override (RLO) Attack</h3><p>This demo will:</p><p>Create a seemingly harmless text file named &ldquo;txt.exe&rdquo; using bidirectional control characters. The file will output the actual and displayed names to show the discrepancy.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.File</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.IOException</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">BidiDemo</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// U+202E is the Right-to-Left Override (RLO) character</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">normalName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"report.txt"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">deceptiveName</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"report"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"\u202E"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"exe.txt"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Try to create files with these names</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">createFile</span><span class="p">(</span><span class="n">normalName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">createFile</span><span class="p">(</span><span class="n">deceptiveName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Print what the names look like to the Java program</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Expected file name: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">normalName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Deceptive file name appears as: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">deceptiveName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">createFile</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">File</span><span class="w"/><span class="n">file</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="n">fileName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">file</span><span class="p">.</span><span class="na">createNewFile</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"File created: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">file</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"File already exists: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">file</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"An error occurred while creating the file: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="explanation">Explanation</h3><p><strong>Creation of Names</strong> : The deceptive file name is created using the right-to-left override character (<code>U+202E</code>). This causes the part of the filename after the bidi character to be interpreted as right-to-left, making &ldquo;exe.txt&rdquo; look like &ldquo;txt.exe&rdquo; in some file systems and interfaces.</p><p><strong>File Creation</strong> : The program attempts to create files with standard and deceptive names.</p><p><strong>Output Differences</strong> : When printed, the deceptive name will show the filename reversed after the bidi character, potentially misleading users about the file type and intent.</p><p>To see the effect:</p><p>- Compile and run the Java program.</p><p>- Check the output and the file system to observe how the filenames are displayed.</p><h3 id="java-demo---right-to-left-mark-rlm-attack">Java Demo - Right-to-Left Mark (RLM) Attack</h3><p>Let&rsquo;s examine a Java example that demonstrates how a Right-to-Left Mark (RLM) can be critical in ensuring the correct display and handling of mixed-direction text. This example will simulate a simple scenario where Arabic and English text are combined, highlighting how the RLM character helps maintain the intended order of words.</p><p>This Java example will:</p><p>1. Combine English and Arabic text in a single string.</p><p>2. Use the Right-to-Left Mark (RLM) to manage the display order correctly.</p><p>3. Print out the results to illustrate the effect of using RLM.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">RLMExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Arabic reads right to left, English left to right</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">englishText</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Version 1.0"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">arabicText</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"الإصدار"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Concatenate without RLM</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">withoutRLM</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">arabicText</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">englishText</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Concatenate with RLM</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">withRLM</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">arabicText</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"\u200F"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">englishText</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Print the results</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Without RLM: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">withoutRLM</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"With RLM: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">withRLM</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="explanation-1">Explanation</h3><p><strong>Arabic and English Text</strong> : Arabic is inherently right-to-left, whereas English is left-to-right.</p><p><strong>Concatenation without RLM</strong> : Depending on the environment, simply concatenating Arabic and English text might not always display correctly, as the directionality of the English text can disrupt the flow of the Arabic.</p><p><strong>Concatenation with RLM</strong> : By inserting a Right-to-Left Mark after the Arabic text but before the English text, the English part is correctly treated as part of the right-to-left sequence. This ensures the English text is read in its natural order but positioned correctly within the overall RTL context.</p><p>When you run this program, especially in a console or environment that supports bidirectional text:</p><p>The &ldquo;Without RLM&rdquo; output may show the English text misplaced or improperly aligned relative to the Arabic text.</p><p>The &ldquo;With RLM&rdquo; output should show the English text correctly placed and maintain the natural reading order of both languages.</p><p>This example underscores the importance of RLM in software and user interfaces dealing with multilingual data. It ensures that text is presented in a way that respects the reading order of different languages. Proper handling of bidirectional text is crucial in applications ranging from document editors to web content management systems.</p><h2 id="but-why-is-this-a-security-issue">But why is this a security issue?</h2><p>Bidirectional control characters like the Right-to-Left Mark (RLM) are a security concern primarily due to their ability to obscure the true intent of text and data. This ability can be exploited in various ways to mislead users or automated systems about the content or function of data, leading to potential security vulnerabilities. Here are some specific scenarios where this becomes critical:</p><h3 id="file-name-spoofing">File Name Spoofing</h3><p>One of the most common security issues related to bidirectional control characters is file name spoofing. Attackers can use bidi characters to reverse the order of characters in a file&rsquo;s extension in file names, making a malicious executable file appear as a harmless type, such as a text file. For instance, the file named<code>doc.exe</code> might be displayed as<code>exe.cod</code> in systems that do not handle bidi characters properly, tricking users into thinking it&rsquo;s merely a document.</p><h3 id="phishing-attacks">Phishing Attacks</h3><p>In phishing emails or misleading links, bidi characters can be used to reverse parts of a URL to mimic a trusted domain, leading users to malicious sites. For example, what appears to be<code>example.com</code> in reversed parts could be a link to an entirely different and dangerous site, exploiting the user&rsquo;s trust in familiar-looking URLs.</p><h3 id="code-obfuscation">Code Obfuscation</h3><p>Developers or malicious coders might use bidi characters to obscure code logic or comments in software, making it difficult for security analysts or automated tools to assess the code&rsquo;s behaviour accurately. This can hide malicious functions or bypass security audits.</p><h3 id="misleading-data-and-database-entries">Misleading Data and Database Entries</h3><p>Bidi characters can be used to reverse strings in database entries, potentially leading to incorrect or misleading data processing. This could be exploited to bypass filters and validation checks or to intentionally corrupt data integrity.</p><h3 id="user-interface-deception">User Interface Deception</h3><p>In applications with user interfaces that display user input data, bidi characters can create a misleading representation of that data. This could need to be clarified for users or lead them to make incorrect decisions based on incorrectly displayed information.</p><h2 id="addressing-the-security-risks">Addressing the Security Risks</h2><p>Addressing the security risks associated with bidirectional control characters (bidi characters) requires a multifaceted approach that includes technical safeguards and user education. Here are more detailed strategies that organizations and software developers can employ to mitigate these risks:</p><h3 id="input-validation-and-sanitization">Input Validation and Sanitization</h3><p><strong>Strict Validation Rules</strong> : Implement strict validation rules that check for the presence of bidi characters in sensitive contexts such as file names, URLs, and input forms. This validation should identify and flag or reject unexpected or unauthorized use of these characters.</p><p><strong>Character Filtering</strong> : For applications not requiring bidi characters, remove them from inputs during the data entry or ingestion process. For applications where such characters are necessary, ensure they are used correctly and safely.</p><p><strong>Encoding Techniques</strong> : Use encoding techniques to handle potentially dangerous characters safely. For example, HTML entities can encode bidi characters in web applications, preventing them from being processed as active components of the code.</p><h3 id="secure-default-configurations">Secure Default Configurations</h3><p><strong>Display Controls</strong> : Configure systems and applications to visually distinguish or neutralize bidi characters, particularly in environments where their use is rare or unexpected. This could involve displaying their unicode point instead of the character or providing visual indicators of text direction changes.</p><p><strong>Limit Usage Contexts</strong> : Restrict the contexts in which bidi characters can be used, especially in identifiers like usernames, filenames, and URLs, unless there is a specific need for them.</p><h3 id="user-and-administrator-education">User and Administrator Education</h3><p><strong>Awareness Training</strong> : Conduct regular training sessions for users and administrators about potentially misusing bidi characters and other Unicode anomalies. Include real-world examples of how these features can be exploited.</p><p><strong>Best Practices for Content Creation</strong> : Educate content creators on the correct and safe use of bidi characters, emphasizing the security aspects of text directionality in content that will be widely distributed or used in sensitive environments.</p><h3 id="enhanced-monitoring-and-logging">Enhanced Monitoring and Logging</h3><p><strong>Anomaly Detection</strong> : Use advanced monitoring tools to detect unusual bidi character usage patterns in system logs, network traffic, or transaction data. This can help identify potential attacks or breaches early.</p><p><strong>Audit Trails</strong> : Maintain robust audit trails, including detailed logging of input validation failures and other security-related events. This can help with forensic analysis and understanding attack vectors after a security incident.</p><h3 id="security-policies-and-procedures">Security Policies and Procedures</h3><p><strong>Clear Policies</strong> : Develop and enforce clear security policies regarding handling bidi characters. This includes guidelines for developers handling text input and output and policies for content managers reviewing and approving content.</p><p><strong>Incident Response</strong> : Include the misuse of bidi characters as a potential vector in your organization&rsquo;s incident response plan. Prepare specific procedures to respond to incidents involving deceptive text or file manipulations.</p><h3 id="technological-solutions">Technological Solutions</h3><p><strong>Development Frameworks and Libraries</strong> : Utilize frameworks and libraries that inherently handle bidi characters safely and transparently. Ensure that these tools are up-to-date and configured correctly.</p><p><strong>User Interface Design</strong> : Design user interfaces that inherently mitigate the risks posed by bidi characters, such as displaying full file extensions and using text elements that visually separate user input from system text.</p><p>Implementing these strategies requires a coordinated effort between software developers, security professionals, system administrators, and end-users. Organizations can significantly reduce the risks of bidi characters and other related security threats by adopting comprehensive measures.</p><h2 id="conclusion">Conclusion:</h2><p>In conclusion, while often overlooked, the security risks associated with bidirectional control characters are significant and can have profound implications for individuals and organizations. These characters can be exploited in various deceptive ways, from file name spoofing and phishing attacks to code obfuscation and misleading data presentations. To effectively mitigate these risks, a comprehensive and multi-layered approach is necessary.</p><p>This approach should include stringent input validation and sanitization processes to filter out or safely handle bidi characters where they are not needed and to ensure they are used appropriately where they are necessary. Secure default configurations that visually indicate the presence and effect of bidi characters can help prevent their misuse, while robust monitoring and logging can aid in detecting and responding to potential security threats.</p><p>Education also plays a crucial role. Users and administrators need to be aware of how bidi characters can be used maliciously, and developers need to be informed about best practices for handling such characters in their code. Security policies must be clear and enforced, with specific guidelines on handling bidi characters effectively and safely.</p><p>Finally, employing technological solutions that can handle these characters appropriately and designing user interfaces that mitigate their risks will further strengthen an organization&rsquo;s defence against the security vulnerabilities introduced by bidirectional control characters. By addressing these issues proactively, we can safeguard the integrity of digital environments and protect sensitive information from being compromised.</p>
]]></content:encoded><category>Java</category><category>Security</category><media:content url="https://svenruppert.com/images/2024/04/E17FE071-E98D-4369-B10F-7CA6F376E428.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/04/E17FE071-E98D-4369-B10F-7CA6F376E428.jpg"/><enclosure url="https://svenruppert.com/images/2024/04/E17FE071-E98D-4369-B10F-7CA6F376E428.jpg" type="image/jpeg" length="0"/></item><item><title>Audio Steganography In More Detail</title><link>https://svenruppert.com/posts/audio-steganography-in-more-detail/</link><pubDate>Wed, 17 Apr 2024 19:22:20 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/audio-steganography-in-more-detail/</guid><description>Audio steganography is a technique for hiding information within an audio file so that only the intended recipient knows of the hidden data&rsquo;s existence. This method belongs to the broader field of steganography, which itself is a subset of security systems and comes from the Greek words &ldquo;steganos,&rdquo; meaning covered, and &ldquo;graphein,&rdquo; meaning writing.</description><content:encoded>&lt;![CDATA[<p>Audio steganography is a technique for hiding information within an audio file so that only the intended recipient knows of the hidden data&rsquo;s existence. This method belongs to the broader field of steganography, which itself is a subset of security systems and comes from the Greek words &ldquo;steganos,&rdquo; meaning covered, and &ldquo;graphein,&rdquo; meaning writing.</p><p>The primary objective of audio steganography is to conceal the presence of secret data by embedding it into an audio signal without noticeable degradation of the signal. Unlike cryptography, which secures the contents of a message through encryption, steganography focuses on concealing the fact that there is a hidden message.</p><h2 id="methods-of-audio-steganography">Methods of Audio Steganography</h2><p>There are various techniques used to embed data within audio files, including:</p><h3 id="least-significant-bit-lsb-insertion">Least Significant Bit (LSB) Insertion:</h3><p>This is one of the simplest methods where bits of the hidden data are inserted into the least significant bits of the audio file. Because these modifications are slight, they are generally imperceptible to the human ear.</p><h3 id="phase-coding">Phase Coding:</h3><p>Phase coding is a sophisticated technique used in audio steganography to embed information within an audio file by manipulating the phase of the sound signal. This method leverages the fact that the human auditory system is far less sensitive to phase changes than it is to changes in amplitude or frequency. As such, phase coding can be a very effective way to hide information without noticeable changes to the sound quality as perceived by human listeners.</p><h4 id="how-phase-coding-works">How Phase Coding Works</h4><p>Phase coding is typically applied to the phase spectrum of a sound signal. The process involves several steps:</p><p><strong>Signal Decomposition:</strong> The original audio signal is divided into segments using a Fast Fourier Transform (FFT) transform. This decomposition converts the time-domain signal into the frequency domain, where each component has an amplitude and a phase.</p><p><strong>Phase Manipulation:</strong> The phase of the audio file&rsquo;s initial segment (or a reference segment) is adjusted to embed the secret data. The phases of subsequent segments are then altered to ensure a smooth transition between segments, maintaining the perceptual integrity of the audio signal. This is crucial to prevent artefacts that could be detectable by the human ear.</p><p><strong>Data Embedding:</strong> The binary data to be hidden is typically encoded in the phase changes between successive segments. A simple method might involve using the presence or absence of a phase shift as a binary one or zero respectively.</p><p><strong>Signal Reconstruction:</strong> After the phase modification, the segments are transformed back to the time domain, recombining them to produce the final audio signal with the hidden data embedded.</p><h4 id="advantages-of-phase-coding">Advantages of Phase Coding</h4><p><strong>Subtlety:</strong> Since human ears are not particularly sensitive to phase variations, especially in complex sounds with many overlapping frequencies, this method can hide data effectively without audible distortion.</p><p><strong>Robustness to Compression:</strong> Phase coding can be relatively robust against lossy compression, especially compared to methods like LSB insertion, which such processes can disrupt.</p><h4 id="challenges-and-considerations">Challenges and Considerations</h4><p><strong>Complexity:</strong> Implementing phase coding requires careful handling to maintain the coherence of the phase between segments. Poor implementation can result in noticeable audio artifacts.</p><p><strong>Data Capacity:</strong> While phase coding is discreet, it typically offers lower data capacity compared to other steganographic techniques. This is because excessive manipulation of phase information can lead to perceptible sound quality degradation or inconsistencies.</p><p><strong>Dependency on Sound Content:</strong> The effectiveness of phase coding can depend heavily on the type of audio being used. Complex signals with many frequency components (like music) offer more opportunities for phase manipulation without detection than more straightforward signals (like speech).</p><p>In summary, phase coding is a powerful but complex technique in audio steganography, offering a high level of discretion for embedding data within audio files. Its successful application requires careful handling to balance data capacity, sound quality, and robustness against signal processing.</p><h3 id="spread-spectrum">Spread Spectrum:</h3><p>In this method, the secret message is spread across the frequency spectrum of the audio file. This spreading makes the message less susceptible to intentional or unintentional modifications.</p><p>This technique is based on spread spectrum communication technology principles, initially developed for military use to ensure secure and robust communication over radio waves.</p><h4 id="how-spread-spectrum-works">How Spread Spectrum Works</h4><p>In the context of audio steganography, spread spectrum involves embedding a secret message into an audio signal by slightly altering its frequency components. The process typically follows these steps:</p><p><strong>Data Preparation:</strong> The data to be hidden is first prepared, often by encoding it in a binary format. This binary data is then spread out over a larger bandwidth than it would normally occupy, which helps in disguising its presence.</p><p><strong>Signal Spreading:</strong> The spread data is combined with a pseudo-noise code (PN code) or a similar key that only the sender and intended recipient know. The PN code has properties similar to noise but with a known structure, making it possible to detect and decode the data without being detected by unintended listeners.</p><p><strong>Modulation:</strong> The combined data and PN code modulate the host audio signal, typically using techniques like Direct Sequence Spread Spectrum (DSSS) or Frequency Hopping Spread Spectrum (FHSS). In DSSS, the signal is spread across a wide range of frequencies based on the PN code. In FHSS, the signal frequency hops rapidly in a pattern defined by the PN code.</p><p><strong>Integration into Audio Signal:</strong> The modulated signal is then subtly integrated into the audio file. This integration is done so that the modifications to the audio are imperceptible to the human ear but can be detected and decoded by analyzing the audio spectrum with the correct key.</p><h4 id="advantages-of-spread-spectrum">Advantages of Spread Spectrum</h4><p><strong>Robustness:</strong> Spread spectrum techniques are highly resistant to interference and noise. Because the data is spread across a wide band of frequencies, it remains intact even if parts of the signal are disrupted or lost.</p><p><strong>Security:</strong> The use of a PN code makes the hidden data difficult to detect and decode without the correct key, providing an additional layer of security.</p><p><strong>Low Detectability:</strong> The hidden data&rsquo;s wide distribution across the frequency spectrum and low amplitude make it difficult to detect through casual listening or even with spectral analysis without prior knowledge.</p><h4 id="challenges-and-considerations-1">Challenges and Considerations</h4><p><strong>Complexity:</strong> Implementing spread spectrum in audio steganography requires sophisticated signal processing techniques and careful tuning to ensure that the hidden data does not affect the quality of the audio.</p><p><strong>Bandwidth Requirements:</strong> The method requires more bandwidth to spread the data, which can be a limitation in environments where bandwidth is constrained.</p><p><strong>Dependency on Audio Content:</strong> Like other steganographic methods, the effectiveness and capacity of the spread spectrum can depend on the nature of the audio content. Complex audio signals with a wide dynamic range and rich frequency content are better for hiding data.</p><h3 id="echo-hiding">Echo Hiding:</h3><p>This involves introducing an echo into the discrete signal. Parameters like the amplitude, decay rate, and offset of the echo can be manipulated to embed data. This method capitalizes on the auditory characteristics of human perception, specifically how humans perceive echoes, to hide data effectively without noticeably altering the quality of the audio.</p><h4 id="how-echo-hiding-works">How Echo Hiding Works</h4><p>Echo hiding works by manipulating the properties of echo—such as delay, decay, and amplitude—to encode data. The basic steps involved in echo hiding are:</p><p><strong>Echo Creation:</strong> Echoes are artificially created and superimposed onto the original audio signal. The parameters of these echoes—such as the delay (time between the original sound and its echo) and the decay rate (how quickly the echo fades away)—are crucial.</p><p><strong>Data Embedding:</strong> Binary data is encoded into the audio signal by varying the parameters of the echoes. For instance, a short delay might represent a binary &lsquo;0&rsquo; while a longer delay might represent a binary &lsquo;1&rsquo;. The amplitude of the echo can also be used to encode data, with different levels of loudness representing different data bits.</p><p><strong>Parameter Control:</strong> To remain imperceptible, the echoes must be subtle. The echo delay is typically kept within the range of 1 to 3 milliseconds, as delays shorter than 1 millisecond are generally not perceived as echoes, and those longer than 3 milliseconds can begin to be perceived as discrete repeats rather than reverberation or natural echo.</p><p><strong>Signal Synthesis:</strong> After encoding the data, the modified signal (original plus echo) is synthesized back into a coherent audio stream. This new audio stream contains the hidden information encoded within the echo parameters but should sound nearly identical to the original to an unsuspecting listener.</p><h4 id="advantages-of-echo-hiding">Advantages of Echo Hiding</h4><p><strong>Imperceptibility:</strong> Since the echoes are subtle and use natural auditory phenomena, the modifications are typically imperceptible to casual listeners.</p><p><strong>Robustness:</strong> Echo properties can be robust against certain types of signal processing, such as compression and transmission over noisy channels because the characteristics of the echo (especially if embedded in a robust part of the audio spectrum) can remain detectable even if the quality of the audio is somewhat degraded.</p><p><strong>Compatibility:</strong> Echo hiding does not require significant alteration of the frequency content of the audio signal, which helps preserve the original quality and characteristics of the audio.</p><h4 id="challenges-and-considerations-2">Challenges and Considerations</h4><p><strong>Detection and Removal:</strong> While robust against some forms of manipulation, sophisticated audio analysis tools designed to identify and modify echo characteristics can detect and potentially remove echoes.</p><p><strong>Capacity Limitations:</strong> Compared to other steganographic methods, echo hiding generally hides a limited amount of data. Overloading the signal with too many echoes can make it more detectable and degrade the audio quality.</p><p><strong>Dependency on Content:</strong> The effectiveness of echo hiding can depend on the nature of the audio content. Audio signals with lots of natural variation and existing reverberation may mask the steganographic echoes better than very dry, sparse, or highly dynamic signals.</p><h3 id="frequency-masking">Frequency Masking:</h3><p>Frequency masking is an audio steganography technique that exploits the limitations of human auditory perception to hide information within an audio file. It leverages a phenomenon known as &ldquo;auditory masking,&rdquo; where certain sounds become inaudible in the presence of other, louder sounds at similar frequencies. This technique is particularly subtle because it embeds data in a way that is naturally concealed by the characteristics of the audio itself.</p><h4 id="how-frequency-masking-works">How Frequency Masking Works</h4><p>The basic concept of frequency masking involves embedding hidden data into parts of the audio spectrum where the presence of louder, dominant sounds will mask it. The process typically involves the following steps:</p><p><strong>Analysis of Audio Spectrum:</strong> The first step is to analyze the frequency spectrum of the host audio signal to identify potential masking opportunities. This involves finding frequency ranges where louder sounds are likely to mask quieter ones.</p><p><strong>Data Preparation:</strong> The data intended for hiding is prepared, usually encoded into a binary format. This data needs to be modulated or otherwise processed to fit within the selected masked frequencies.</p><p><strong>Embedding Data:</strong> The prepared data is then embedded into the quieter parts of the audio spectrum, specifically within the critical bands of frequency where masking is most effective. Critical bands are frequency ranges in which the human ear processes sound as a single auditory event.</p><p><strong>Signal Synthesis:</strong> After embedding the data, the audio signal is reconstructed to include the hidden data. This process must be handled delicately to ensure that the modifications do not become perceptible, maintaining the quality and integrity of the original audio.</p><h4 id="advantages-of-frequency-masking">Advantages of Frequency Masking</h4><p><strong>Imperceptibility:</strong> Because the hidden data is placed in regions where louder sounds naturally mask it, it is very difficult to detect without specific, sophisticated analysis tools.</p><p><strong>Robustness to Compression:</strong> Frequency masking can be relatively robust against some forms of audio compression, particularly if the embedded data is strategically placed in less compressible parts of the spectrum.</p><p><strong>Utilization of Auditory Phenomena:</strong> This method uses a natural auditory phenomenon, making it a very organic form of steganography.</p><h4 id="challenges-and-considerations-3">Challenges and Considerations</h4><p><strong>Complex Signal Analysis Required:</strong> Effective use of frequency masking requires detailed analysis of the audio signal’s spectral properties, which can be complex and computationally intensive.</p><p><strong>Limited Data Capacity:</strong> The amount of data that can be hidden is generally limited to the available masked regions, which may not be extensive depending on the audio content.</p><p><strong>Dependency on Audio Content:</strong> The success of frequency masking heavily depends on the nature of the audio file. Audio tracks with dense and varied spectral content provide more opportunities for masking than simpler, cleaner tracks.</p><h2 id="applications">Applications</h2><p>Audio steganography has various applications across multiple fields. Some of these include:</p><p><strong>Secure Communications:</strong> These are used by organizations and individuals to communicate sensitive information discreetly.</p><p><strong>Watermarking:</strong> Audio files can be watermarked to assert ownership, much like watermarking images or videos.</p><p><strong>Covert Operations:</strong> Used by law enforcement and military for operations requiring secure and stealthy communication methods.</p><h2 id="challenges">Challenges</h2><p>Despite its advantages, audio steganography faces several challenges:</p><h3 id="robustness">Robustness:</h3><p>The steganographic information must remain intact even if the audio file undergoes compression, format conversion, or other types of digital processing.</p><h3 id="imperceptibility">Imperceptibility:</h3><p>The alterations made to embed the data should not be detectable by normal hearing, as this would compromise the steganographic integrity.</p><h3 id="capacity">Capacity:</h3><p>The amount of data that can be hidden is generally limited by the size of the host file and the technique used, which could be restrictive for larger data needs.</p><h2 id="conclusion">Conclusion</h2><p>Audio steganography offers a unique way to conceal information within audio files, making it a valuable tool for security and privacy in digital communications. Its effectiveness lies in its ability to hide information in plain sight, providing an added layer of security through obscurity. However, its success and reliability depend heavily on the choice of technique and the nature of the application. As technology evolves, so do steganography methods, which are continuously improving to meet the demands of modern security needs.</p>
]]></content:encoded><category>Security</category><media:content url="https://svenruppert.com/images/2024/04/22C2881A-AA3A-4DB1-A857-54C47CF1FB00.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/04/22C2881A-AA3A-4DB1-A857-54C47CF1FB00.jpg"/><enclosure url="https://svenruppert.com/images/2024/04/22C2881A-AA3A-4DB1-A857-54C47CF1FB00.jpg" type="image/jpeg" length="0"/></item><item><title>Beyond the Visible: Exploring the Depths of Steganography</title><link>https://svenruppert.com/posts/beyond-the-visible-exploring-the-depths-of-steganography/</link><pubDate>Thu, 28 Mar 2024 14:02:52 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/beyond-the-visible-exploring-the-depths-of-steganography/</guid><description>Steganography is the practice of concealing a message, file, image, or video within another message, file, image, or video. Unlike cryptography, which focuses on making a message unreadable to unauthorised parties, steganography aims to hide the message&rsquo;s existence. The word &ldquo;steganography " is derived from the Greek words &ldquo;steganos ,&rdquo; meaning &ldquo;covered ,&rdquo; and &ldquo;graphein ,&rdquo; meaning &ldquo;to write.&rdquo;</description><content:encoded>&lt;![CDATA[<p>Steganography is the practice of concealing a message, file, image, or video within another message, file, image, or video. Unlike cryptography, which focuses on making a message unreadable to unauthorised parties, steganography aims to hide the message&rsquo;s existence. The word &ldquo;<strong>steganography</strong> " is derived from the Greek words &ldquo;<strong>steganos</strong> ,&rdquo; meaning &ldquo;<strong>covered</strong> ,&rdquo; and &ldquo;<strong>graphein</strong> ,&rdquo; meaning &ldquo;<strong>to write</strong>.&rdquo;</p><ol><li><a href="https://svenruppert.com/2024/03/28/beyond-the-visible-exploring-the-depths-of-steganography/#an-example-from-history">An example from history</a></li><li><a href="https://svenruppert.com/2024/03/28/beyond-the-visible-exploring-the-depths-of-steganography/#example-of-using-steganography-in-cybersecurity">Example of using Steganography in Cybersecurity</a></li><li><a href="https://svenruppert.com/2024/03/28/beyond-the-visible-exploring-the-depths-of-steganography/#how-to-do-it-practically-in-java">How to do it practically in Java</a><ol><li><a href="https://svenruppert.com/2024/03/28/beyond-the-visible-exploring-the-depths-of-steganography/#text-steganography">Text Steganography:</a></li><li><a href="https://svenruppert.com/2024/03/28/beyond-the-visible-exploring-the-depths-of-steganography/#image-steganography">Image Steganography:</a></li><li><a href="https://svenruppert.com/2024/03/28/beyond-the-visible-exploring-the-depths-of-steganography/#audio-steganography">Audio Steganography:</a></li><li><a href="https://svenruppert.com/2024/03/28/beyond-the-visible-exploring-the-depths-of-steganography/#video-steganography">Video Steganography:</a></li><li><a href="https://svenruppert.com/2024/03/28/beyond-the-visible-exploring-the-depths-of-steganography/#file-steganography">File Steganography:</a></li></ol></li><li><a href="https://svenruppert.com/2024/03/28/beyond-the-visible-exploring-the-depths-of-steganography/#conclusion">Conclusion:</a></li></ol><h2 id="an-example-from-history">An example from history</h2><p>One notable historical example of steganography involves using invisible ink during the American Revolutionary War. The British and American forces employed various forms of steganography to conceal messages and strategic information.</p><p>In particular, the Culper Spy Ring, a clandestine network of American spies operating during the Revolutionary War, extensively used invisible ink to communicate covertly. One member of the ring, invisible ink expert James Jay, developed a secret method for creating invisible ink using simple household ingredients such as lemon juice or milk.</p><p>The Culper Spy Ring would write messages with this invisible ink between the lines of innocent-looking letters or on the blank spaces of documents. To reveal the hidden messages, the recipient would apply heat or special chemicals, causing the invisible ink to darken and become visible.</p><p>This use of steganography allowed the Culper Spy Ring to transmit crucial intelligence about British troop movements, plans, and other sensitive information without detection by British authorities. The effective use of invisible ink by the Culper Spy Ring played a significant role in aiding the American cause during the Revolutionary War and exemplifies the historical importance of steganography in espionage and covert operations.</p><p><a href="https://youtu.be/tYeC3A57wgc">https://youtu.be/tYeC3A57wgc</a></p><h2 id="example-of-using-steganography-in-cybersecurity">Example of using Steganography in Cybersecurity</h2><p>A modern example of steganography in cybersecurity involves concealing malicious code within seemingly innocuous digital files to bypass security measures and deliver malware to targeted systems. Cybercriminals often employ this technique to evade detection by traditional antivirus software and intrusion detection systems.</p><p>For instance, attackers may embed malicious payloads, such as Trojans or ransomware, within images, audio files, or documents using steganography techniques. The carrier files appear unchanged to the naked eye or standard file analysis tools, making it difficult for security solutions to detect hidden malware.</p><p>Once the steganographically encoded file reaches the target system, the attacker can extract and execute the concealed payload, thereby compromising the system and initiating malicious activities.</p><p>To combat this threat, cybersecurity professionals utilise advanced threat detection technologies capable of analysing files for signs of steganographic manipulation. These tools employ various techniques, such as statistical analysis, anomaly detection, and signature-based detection, to identify suspicious patterns or deviations from expected file structures.</p><p>Additionally, cybersecurity awareness training programs educate users about the risks associated with opening files from untrusted sources and emphasise the importance of maintaining up-to-date security software to mitigate the threat of steganography-based attacks.</p><div class="pull-quote"><p>All Code Examples are on GitHub:<a href="https://github.com/svenruppert/Steganography">https://github.com/svenruppert/Steganography</a></p></div><h2 id="how-to-do-it-practically-in-java">How to do it practically in Java</h2><p>Steganography techniques can involve various methods, such as:</p><div class="pull-quote"><p>The source code you will find on GitHub under the following URL:</p><p><a href="https://github.com/svenruppert/Steganography">https://github.com/svenruppert/Steganography</a></p></div><h3 id="text-steganography">Text Steganography:</h3><p>Embedding secret messages within text documents by altering spacing, font styles, or using invisible characters.</p><p>Here&rsquo;s a simple example of text steganography in Java using a basic technique called whitespace steganography. In this example, we&rsquo;ll hide a secret message within a text document by manipulating the whitespace between words.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">TextSteganographyExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">4</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">5</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">6</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">binaryData</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"101"</span><span class="p">;</span><span class="w"/><span class="c1">// Binary data to hide</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">7</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">sourceText</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Hello\nWorld\nThis is a test\n"</span><span class="p">;</span><span class="w"/><span class="c1">// Source text to hide data in</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">8</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">hiddenText</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">hideData</span><span class="p">(</span><span class="n">sourceText</span><span class="p">,</span><span class="w"/><span class="n">binaryData</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">9</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">10</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Text without hidden data:"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">11</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">sourceText</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">12</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Text with hidden data:"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">13</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">hiddenText</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">14</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">15</span><span class="w"/><span class="c1">//extract the hidden message</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">16</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">extractedData</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">extractData</span><span class="p">(</span><span class="n">hiddenText</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">17</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"extractedData = "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">extractedData</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">18</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">19</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">20</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">hideData</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">sourceText</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">binaryData</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">21</span><span class="w"/><span class="n">Scanner</span><span class="w"/><span class="n">scanner</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Scanner</span><span class="p">(</span><span class="n">sourceText</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">22</span><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">hiddenTextBuilder</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">23</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">index</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">24</span><span class="w"/><span class="nf">while</span><span class="w"/><span class="p">(</span><span class="n">scanner</span><span class="p">.</span><span class="na">hasNextLine</span><span class="p">()</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">index</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">binaryData</span><span class="p">.</span><span class="na">length</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">25</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">line</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">scanner</span><span class="p">.</span><span class="na">nextLine</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">26</span><span class="w"/><span class="c1">// Append a space for '0', or a tab for '1'</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">27</span><span class="w"/><span class="kt">char</span><span class="w"/><span class="n">appendChar</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">binaryData</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">index</span><span class="p">)</span><span class="w"/><span class="o">==</span><span class="w"/><span class="sc">'0'</span><span class="w"/><span class="o">?</span><span class="w"/><span class="sc">' '</span><span class="w"/><span class="p">:</span><span class="w"/><span class="sc">'\t'</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">28</span><span class="w"/><span class="n">hiddenTextBuilder</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">line</span><span class="p">).</span><span class="na">append</span><span class="p">(</span><span class="n">appendChar</span><span class="p">).</span><span class="na">append</span><span class="p">(</span><span class="s">"\n"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">29</span><span class="w"/><span class="n">index</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">30</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">31</span><span class="w"/><span class="c1">// If there's more of the source text, add it as is</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">32</span><span class="w"/><span class="nf">while</span><span class="w"/><span class="p">(</span><span class="n">scanner</span><span class="p">.</span><span class="na">hasNextLine</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">33</span><span class="w"/><span class="n">hiddenTextBuilder</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">scanner</span><span class="p">.</span><span class="na">nextLine</span><span class="p">()).</span><span class="na">append</span><span class="p">(</span><span class="s">"\n"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">34</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">35</span><span class="w"/><span class="n">scanner</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">36</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">hiddenTextBuilder</span><span class="p">.</span><span class="na">toString</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">37</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">38</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">39</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">extractData</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">hiddenText</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">40</span><span class="w"/><span class="n">Scanner</span><span class="w"/><span class="n">scanner</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Scanner</span><span class="p">(</span><span class="n">hiddenText</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">41</span><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">binaryDataBuilder</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">42</span><span class="w"/><span class="nf">while</span><span class="w"/><span class="p">(</span><span class="n">scanner</span><span class="p">.</span><span class="na">hasNextLine</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">43</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">line</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">scanner</span><span class="p">.</span><span class="na">nextLine</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">44</span><span class="w"/><span class="nf">if</span><span class="w"/><span class="p">(</span><span class="n">line</span><span class="p">.</span><span class="na">endsWith</span><span class="p">(</span><span class="s">"\t"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">45</span><span class="w"/><span class="c1">// If line ends with a tab, append '1'</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">46</span><span class="w"/><span class="n">binaryDataBuilder</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="sc">'1'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">47</span><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">line</span><span class="p">.</span><span class="na">endsWith</span><span class="p">(</span><span class="s">" "</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">48</span><span class="w"/><span class="c1">// If line ends with a space, append '0'</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">49</span><span class="w"/><span class="n">binaryDataBuilder</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="sc">'0'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">50</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">51</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">52</span><span class="w"/><span class="n">scanner</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">53</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">binaryDataBuilder</span><span class="p">.</span><span class="na">toString</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">54</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">55</span><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>This code demonstrates a basic form of text steganography where a secret message is hidden within the whitespace of a text document. Note that this method needs to be revised and may not be suitable for high-security applications. Advanced techniques can involve more sophisticated manipulation of text or embedding data within specific patterns.</p><h3 id="image-steganography">Image Steganography:</h3><p>Data is concealed within digital images by modifying the least significant bits of pixel values or by embedding data within specific regions of the image where slight alterations are less noticeable.</p><p>Certainly! Here&rsquo;s a simple example of image steganography in Java using LSB (Least Significant Bit) embedding. In this example, we&rsquo;ll hide a secret message within the least significant bits of an image&rsquo;s pixels.</p><p>This code hides a secret message within the least significant bit of each pixel in the image. The original image is saved as a new steganographic image when the message is hidden. Later, the hidden message is extracted by reading the steganographic image&rsquo;s pixels and retrieving the least significant bit of the red component of each pixel. Finally, the binary message is converted back into a readable string. Replace &ldquo;input_image.png&rdquo; with the path to your input image.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ImageSteganography</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">8</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">9</span><span class="w"/><span class="c1">// Method to encode a message into an image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">10</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">BufferedImage</span><span class="w"/><span class="nf">encodeMessage</span><span class="p">(</span><span class="n">BufferedImage</span><span class="w"/><span class="n">image</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">message</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">11</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">messageLength</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">message</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">12</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">imageWidth</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getWidth</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">13</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">imageHeight</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getHeight</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">14</span><span class="w"/><span class="kt">int</span><span class="o">[]</span><span class="w"/><span class="n">imagePixels</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">int</span><span class="o">[</span><span class="n">imageWidth</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">imageHeight</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">15</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getRGB</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">imageWidth</span><span class="p">,</span><span class="w"/><span class="n">imageHeight</span><span class="p">,</span><span class="w"/><span class="n">imagePixels</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">imageWidth</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">16</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">17</span><span class="w"/><span class="c1">// Convert the message into a binary string</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">18</span><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">binaryMessage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">19</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">char</span><span class="w"/><span class="n">character</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">message</span><span class="p">.</span><span class="na">toCharArray</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">20</span><span class="w"/><span class="n">binaryMessage</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">"%8s"</span><span class="p">,</span><span class="w"/><span class="n">Integer</span><span class="p">.</span><span class="na">toBinaryString</span><span class="p">(</span><span class="n">character</span><span class="p">)).</span><span class="na">replaceAll</span><span class="p">(</span><span class="s">" "</span><span class="p">,</span><span class="w"/><span class="s">"0"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">21</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">22</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">23</span><span class="w"/><span class="c1">// Encode the message length at the beginning</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">24</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">messageLengthBinary</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">"%32s"</span><span class="p">,</span><span class="w"/><span class="n">Integer</span><span class="p">.</span><span class="na">toBinaryString</span><span class="p">(</span><span class="n">messageLength</span><span class="p">)).</span><span class="na">replace</span><span class="p">(</span><span class="sc">' '</span><span class="p">,</span><span class="w"/><span class="sc">'0'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">25</span><span class="w"/><span class="n">binaryMessage</span><span class="p">.</span><span class="na">insert</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">messageLengthBinary</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">26</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">27</span><span class="w"/><span class="c1">// Encode the binary message into the image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">28</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">binaryMessage</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">29</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">pixel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">imagePixels</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">30</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">blue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">pixel</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">31</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">green</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">pixel</span><span class="w"/><span class="o">&gt;&gt;</span><span class="w"/><span class="n">8</span><span class="p">)</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">32</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">red</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">pixel</span><span class="w"/><span class="o">&gt;&gt;</span><span class="w"/><span class="n">16</span><span class="p">)</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">33</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">alpha</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">pixel</span><span class="w"/><span class="o">&gt;&gt;</span><span class="w"/><span class="n">24</span><span class="p">)</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">34</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">35</span><span class="w"/><span class="c1">// Modify the LSB of the blue part of the pixel to match the current bit of the message</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">36</span><span class="w"/><span class="n">blue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">blue</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFE</span><span class="p">)</span><span class="w"/><span class="o">|</span><span class="w"/><span class="p">(</span><span class="n">binaryMessage</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">i</span><span class="p">)</span><span class="w"/><span class="o">-</span><span class="w"/><span class="sc">'0'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">37</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">newPixel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">alpha</span><span class="w"/><span class="o">&lt;&lt;</span><span class="w"/><span class="n">24</span><span class="p">)</span><span class="w"/><span class="o">|</span><span class="w"/><span class="p">(</span><span class="n">red</span><span class="w"/><span class="o">&lt;&lt;</span><span class="w"/><span class="n">16</span><span class="p">)</span><span class="w"/><span class="o">|</span><span class="w"/><span class="p">(</span><span class="n">green</span><span class="w"/><span class="o">&lt;&lt;</span><span class="w"/><span class="n">8</span><span class="p">)</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">blue</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">38</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">39</span><span class="w"/><span class="n">imagePixels</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">newPixel</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">40</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">41</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">42</span><span class="w"/><span class="n">BufferedImage</span><span class="w"/><span class="n">newImage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">BufferedImage</span><span class="p">(</span><span class="n">imageWidth</span><span class="p">,</span><span class="w"/><span class="n">imageHeight</span><span class="p">,</span><span class="w"/><span class="n">BufferedImage</span><span class="p">.</span><span class="na">TYPE_INT_ARGB</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">43</span><span class="w"/><span class="n">newImage</span><span class="p">.</span><span class="na">setRGB</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">imageWidth</span><span class="p">,</span><span class="w"/><span class="n">imageHeight</span><span class="p">,</span><span class="w"/><span class="n">imagePixels</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">imageWidth</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">44</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">newImage</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">45</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">46</span><span class="w"/><span class="c1">// Method to decode the hidden message from an image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">47</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">decodeMessage</span><span class="p">(</span><span class="n">BufferedImage</span><span class="w"/><span class="n">image</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">48</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">imageWidth</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getWidth</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">49</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">imageHeight</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getHeight</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">50</span><span class="w"/><span class="kt">int</span><span class="o">[]</span><span class="w"/><span class="n">imagePixels</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">int</span><span class="o">[</span><span class="n">imageWidth</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">imageHeight</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">51</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getRGB</span><span class="p">(</span><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">imageWidth</span><span class="p">,</span><span class="w"/><span class="n">imageHeight</span><span class="p">,</span><span class="w"/><span class="n">imagePixels</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">imageWidth</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">52</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">53</span><span class="w"/><span class="c1">// Extract the length of the message</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">54</span><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">messageLengthBinary</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">55</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">32</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">56</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">pixel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">imagePixels</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">57</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">blue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">pixel</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">58</span><span class="w"/><span class="n">messageLengthBinary</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">blue</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">59</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">60</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">messageLength</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Integer</span><span class="p">.</span><span class="na">parseInt</span><span class="p">(</span><span class="n">messageLengthBinary</span><span class="p">.</span><span class="na">toString</span><span class="p">(),</span><span class="w"/><span class="n">2</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">61</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">62</span><span class="w"/><span class="c1">// Extract the binary message from the image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">63</span><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">binaryMessage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">64</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">32</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">32</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">messageLength</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">8</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">65</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">pixel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">imagePixels</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">66</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">blue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">pixel</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">67</span><span class="w"/><span class="n">binaryMessage</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">blue</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">1</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">68</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">69</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">70</span><span class="w"/><span class="c1">// Convert the binary message to string</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">71</span><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">message</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">72</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">binaryMessage</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">+=</span><span class="w"/><span class="n">8</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">73</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">byteString</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">binaryMessage</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">i</span><span class="p">,</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">74</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">charCode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Integer</span><span class="p">.</span><span class="na">parseInt</span><span class="p">(</span><span class="n">byteString</span><span class="p">,</span><span class="w"/><span class="n">2</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">75</span><span class="w"/><span class="n">message</span><span class="p">.</span><span class="na">append</span><span class="p">((</span><span class="kt">char</span><span class="p">)</span><span class="w"/><span class="n">charCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">76</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">77</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">78</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">message</span><span class="p">.</span><span class="na">toString</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">79</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">80</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">81</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">originalImageFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"_data/_DSC1259.png"</span><span class="p">);</span><span class="w"/><span class="c1">// Specify the path to the input image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">82</span><span class="w"/><span class="n">BufferedImage</span><span class="w"/><span class="n">originalImage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ImageIO</span><span class="p">.</span><span class="na">read</span><span class="p">(</span><span class="n">originalImageFile</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">83</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">secretMessage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Secret message goes here"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">84</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">85</span><span class="w"/><span class="c1">// Encode the message into the image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">86</span><span class="w"/><span class="n">BufferedImage</span><span class="w"/><span class="n">encodedImage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">encodeMessage</span><span class="p">(</span><span class="n">originalImage</span><span class="p">,</span><span class="w"/><span class="n">secretMessage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">87</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">88</span><span class="w"/><span class="c1">// Save the encoded image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">89</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">outputImageFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"_data/_DSC1259_with-data.png"</span><span class="p">);</span><span class="w"/><span class="c1">// Specify the path to the output image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">90</span><span class="w"/><span class="n">ImageIO</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">encodedImage</span><span class="p">,</span><span class="w"/><span class="s">"png"</span><span class="p">,</span><span class="w"/><span class="n">outputImageFile</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">91</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The message has been encoded into the image."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">92</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">93</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">94</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">imageFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"_data/_DSC1259_with-data.png"</span><span class="p">);</span><span class="w"/><span class="c1">// Specify the path to the encoded image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">95</span><span class="w"/><span class="n">BufferedImage</span><span class="w"/><span class="n">image</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ImageIO</span><span class="p">.</span><span class="na">read</span><span class="p">(</span><span class="n">imageFile</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">96</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">97</span><span class="w"/><span class="c1">// Decode the message from the image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">98</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">decodedMessage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">decodeMessage</span><span class="p">(</span><span class="n">image</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">99</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The hidden message is: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">decodedMessage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">100</span><span class="w"/><span class="nf">calculatingMaxInfoAmount</span><span class="p">(</span><span class="s">"_data/_DSC1259_with-data.png"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">101</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">102</span><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">calculatingMaxInfoAmount</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">pathname</span><span class="p">){</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">103</span><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">104</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">imageFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="n">pathname</span><span class="p">);</span><span class="w"/><span class="c1">// Specify the path to your image</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">105</span><span class="w"/><span class="n">BufferedImage</span><span class="w"/><span class="n">image</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">ImageIO</span><span class="p">.</span><span class="na">read</span><span class="p">(</span><span class="n">imageFile</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">106</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">imageWidth</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getWidth</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">107</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">imageHeight</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getHeight</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">108</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">maxBits</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="w"/><span class="n">imageWidth</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">imageHeight</span><span class="p">;</span><span class="w"/><span class="c1">// Maximum bits that can be stored</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">109</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">maxBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">maxBits</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">8</span><span class="p">;</span><span class="w"/><span class="c1">// Convert bits to bytes</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">110</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">111</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Maximum information that can be stored in bytes: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">maxBytes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">112</span><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">113</span><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="nf">RuntimeException</span><span class="p">(</span><span class="n">e</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">114</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">115</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">116</span><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h3 id="audio-steganography">Audio Steganography:</h3><p>Hiding information within audio files by modifying the least significant bits of audio samples or by exploiting imperceptible frequencies.</p><p>Here&rsquo;s a basic example of audio steganography in Java using LSB (Least Significant Bit) embedding. In this example, we&rsquo;ll hide a secret message within the least significant bits of the audio samples.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">AudioSteganography</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">6</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">7</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">8</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">inputFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"_data/Security - 2024.06 - What is Steganography-16bit-single-track_A01_L.wav"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">9</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">outputFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"_data/output.wav"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">10</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">message</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Secret Message"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">11</span><span class="w"/><span class="nf">hideMessage</span><span class="p">(</span><span class="n">inputFile</span><span class="p">,</span><span class="w"/><span class="n">outputFile</span><span class="p">,</span><span class="w"/><span class="n">message</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">12</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">13</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">inputFileEncoded</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"_data/output.wav"</span><span class="p">);</span><span class="w"/><span class="c1">// This is the file with the hidden message</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">14</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">extractedMessage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">extractMessage</span><span class="p">(</span><span class="n">inputFileEncoded</span><span class="p">,</span><span class="w"/><span class="n">14</span><span class="p">);</span><span class="w"/><span class="c1">// Assuming we know the message length</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">15</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Extracted Message: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">extractedMessage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">16</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">17</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">18</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">hideMessage</span><span class="p">(</span><span class="n">File</span><span class="w"/><span class="n">inputFile</span><span class="p">,</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">outputFile</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">message</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">19</span><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">20</span><span class="w"/><span class="c1">// Convert the message to a binary string</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">21</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">messageBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">message</span><span class="p">.</span><span class="na">getBytes</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">22</span><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">binaryMessage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">23</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="w"/><span class="n">b</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">messageBytes</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">24</span><span class="w"/><span class="n">binaryMessage</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">String</span><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">"%8s"</span><span class="p">,</span><span class="w"/><span class="n">Integer</span><span class="p">.</span><span class="na">toBinaryString</span><span class="p">(</span><span class="n">b</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">)).</span><span class="na">replace</span><span class="p">(</span><span class="sc">' '</span><span class="p">,</span><span class="w"/><span class="sc">'0'</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">25</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">26</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">messageBinary</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">binaryMessage</span><span class="p">.</span><span class="na">toString</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">27</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">28</span><span class="w"/><span class="c1">// Load the audio file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">29</span><span class="w"/><span class="n">AudioInputStream</span><span class="w"/><span class="n">audioInputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">AudioSystem</span><span class="p">.</span><span class="na">getAudioInputStream</span><span class="p">(</span><span class="n">inputFile</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">30</span><span class="w"/><span class="n">AudioFormat</span><span class="w"/><span class="n">format</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">audioInputStream</span><span class="p">.</span><span class="na">getFormat</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">31</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">audioBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">audioInputStream</span><span class="p">.</span><span class="na">readAllBytes</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">32</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">33</span><span class="w"/><span class="c1">// Hide the message in the LSB of the audio bytes</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">34</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">messageIndex</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">35</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">audioBytes</span><span class="p">.</span><span class="na">length</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">messageIndex</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">messageBinary</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">+=</span><span class="w"/><span class="n">2</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="c1">// Skip every other byte for 16-bit samples</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">36</span><span class="w"/><span class="nf">if</span><span class="w"/><span class="p">(</span><span class="n">messageBinary</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">messageIndex</span><span class="p">)</span><span class="w"/><span class="o">==</span><span class="w"/><span class="sc">'1'</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">37</span><span class="w"/><span class="n">audioBytes</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="p">(</span><span class="n">audioBytes</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">1</span><span class="p">);</span><span class="w"/><span class="c1">// Set LSB to 1</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">38</span><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">39</span><span class="w"/><span class="n">audioBytes</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="kt">byte</span><span class="p">)</span><span class="w"/><span class="p">(</span><span class="n">audioBytes</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="o">~</span><span class="n">1</span><span class="p">);</span><span class="w"/><span class="c1">// Set LSB to 0</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">40</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">41</span><span class="w"/><span class="n">messageIndex</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">42</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">43</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">44</span><span class="w"/><span class="c1">// Write the modified samples to a new file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">45</span><span class="w"/><span class="n">ByteArrayInputStream</span><span class="w"/><span class="n">bais</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ByteArrayInputStream</span><span class="p">(</span><span class="n">audioBytes</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">46</span><span class="w"/><span class="n">AudioInputStream</span><span class="w"/><span class="n">outputAudioInputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">AudioInputStream</span><span class="p">(</span><span class="n">bais</span><span class="p">,</span><span class="w"/><span class="n">format</span><span class="p">,</span><span class="w"/><span class="n">audioBytes</span><span class="p">.</span><span class="na">length</span><span class="w"/><span class="o">/</span><span class="w"/><span class="n">format</span><span class="p">.</span><span class="na">getFrameSize</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">47</span><span class="w"/><span class="n">AudioSystem</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">outputAudioInputStream</span><span class="p">,</span><span class="w"/><span class="n">AudioFileFormat</span><span class="p">.</span><span class="na">Type</span><span class="p">.</span><span class="na">WAVE</span><span class="p">,</span><span class="w"/><span class="n">outputFile</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">48</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">49</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The message has been hidden in "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">outputFile</span><span class="p">.</span><span class="na">getName</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">50</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">51</span><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">UnsupportedAudioFileException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">52</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">53</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">54</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">55</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">56</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">extractMessage</span><span class="p">(</span><span class="n">File</span><span class="w"/><span class="n">inputFile</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">messageLength</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">57</span><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">58</span><span class="w"/><span class="c1">// Load the audio file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">59</span><span class="w"/><span class="n">AudioInputStream</span><span class="w"/><span class="n">audioInputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">AudioSystem</span><span class="p">.</span><span class="na">getAudioInputStream</span><span class="p">(</span><span class="n">inputFile</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">60</span><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">audioBytes</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">audioInputStream</span><span class="p">.</span><span class="na">readAllBytes</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">61</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">62</span><span class="w"/><span class="c1">// Extract bits to reconstruct the message binary string</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">63</span><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">messageBinary</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">64</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">messageLength</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">8</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">2</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">+=</span><span class="w"/><span class="n">2</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/><span class="c1">// Assuming 16-bit samples, adjust for actual sample size</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">65</span><span class="w"/><span class="kt">byte</span><span class="w"/><span class="n">b</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">audioBytes</span><span class="o">[</span><span class="n">i</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">66</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">lsb</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">b</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">1</span><span class="p">;</span><span class="w"/><span class="c1">// Extract the LSB</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">67</span><span class="w"/><span class="n">messageBinary</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">lsb</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">68</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">69</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">70</span><span class="w"/><span class="c1">// Convert the binary string to text</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">71</span><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">message</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">72</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">messageBinary</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">+=</span><span class="w"/><span class="n">8</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">73</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">byteString</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">messageBinary</span><span class="p">.</span><span class="na">substring</span><span class="p">(</span><span class="n">i</span><span class="p">,</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">8</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">74</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">charCode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Integer</span><span class="p">.</span><span class="na">parseInt</span><span class="p">(</span><span class="n">byteString</span><span class="p">,</span><span class="w"/><span class="n">2</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">75</span><span class="w"/><span class="n">message</span><span class="p">.</span><span class="na">append</span><span class="p">((</span><span class="kt">char</span><span class="p">)</span><span class="w"/><span class="n">charCode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">76</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">77</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">78</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">message</span><span class="p">.</span><span class="na">toString</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">79</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">80</span><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">UnsupportedAudioFileException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">81</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">82</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">83</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">84</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">85</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">86</span><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>This code hides a secret message within the input audio file&rsquo;s least significant bit of each audio sample. The modified audio data is then saved as a new steganographic audio file. Later, the hidden message is extracted by reading the least significant bit of each audio sample in the steganographic audio file. Replace filename and path with the path to your input audio file.</p><h3 id="video-steganography">Video Steganography:</h3><p>Concealing data within video files by manipulating frames or embedding data in specific segments.</p><p>Below is a basic example of video steganography in Java using LSB (Least Significant Bit) embedding. In this example, we&rsquo;ll hide a secret message within the least significant bits of the blue pixel values of video frames.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">VideoSteganography</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">14</span><span class="w"/><span class="c1">//Should be extracted from the orig video</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">15</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">FPS</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">50</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">16</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">INPUT_FILE</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"input.mp4"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">17</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">OUTPUT_FILE</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"output.mp4"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">18</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">19</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">Exception</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">20</span><span class="w"/><span class="n">FileChannelWrapper</span><span class="w"/><span class="n">fileChannelWrapperIN</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">NIOUtils</span><span class="p">.</span><span class="na">readableChannel</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="n">INPUT_FILE</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">21</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">outputFile</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="n">OUTPUT_FILE</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">22</span><span class="w"/><span class="c1">// Create a SequenceEncoder for the output file at 25 frames per second</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">23</span><span class="w"/><span class="n">SequenceEncoder</span><span class="w"/><span class="n">encoder</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">SequenceEncoder</span><span class="p">.</span><span class="na">createSequenceEncoder</span><span class="p">(</span><span class="n">outputFile</span><span class="p">,</span><span class="w"/><span class="n">FPS</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">24</span><span class="w"/><span class="n">FrameGrab</span><span class="w"/><span class="n">grab</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">FrameGrab</span><span class="p">.</span><span class="na">createFrameGrab</span><span class="p">(</span><span class="n">fileChannelWrapperIN</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">25</span><span class="w"/><span class="n">Picture</span><span class="w"/><span class="n">picture</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">26</span><span class="w"/><span class="c1">// to improve to process</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">27</span><span class="w"/><span class="c1">// 0. detect the FPS from the input video</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">28</span><span class="w"/><span class="c1">// 1. calculate the max amount of possible bytes that can be encoded</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">29</span><span class="w"/><span class="c1">// 2. calculate the max amount of bytes that can be stored in each frame</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">30</span><span class="w"/><span class="c1">// 3. split the message into chunks to fit into a frame</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">31</span><span class="w"/><span class="c1">// 4. define a sequence to mark the end of the message</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">32</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">33</span><span class="w"/><span class="nf">while</span><span class="w"/><span class="p">(</span><span class="kc">null</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="p">(</span><span class="n">picture</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">grab</span><span class="p">.</span><span class="na">getNativeFrame</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">34</span><span class="w"/><span class="c1">// Here, convert the Picture to BufferedImage</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">35</span><span class="w"/><span class="n">BufferedImage</span><span class="w"/><span class="n">frame</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">AWTUtil</span><span class="p">.</span><span class="na">toBufferedImage</span><span class="p">(</span><span class="n">picture</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">36</span><span class="w"/><span class="c1">// Modify the frame to encode part of the message</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">37</span><span class="w"/><span class="n">BufferedImage</span><span class="w"/><span class="n">encodedMessageOnBlue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">encodeMessageOnBlue</span><span class="p">(</span><span class="n">frame</span><span class="p">,</span><span class="w"/><span class="n">toBinaryString</span><span class="p">(</span><span class="s">"Hello Message"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">38</span><span class="w"/><span class="c1">// Convert BufferedImage to Picture (required by JCodec)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">39</span><span class="w"/><span class="n">Picture</span><span class="w"/><span class="n">pic</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">AWTUtil</span><span class="p">.</span><span class="na">fromBufferedImage</span><span class="p">(</span><span class="n">encodedMessageOnBlue</span><span class="p">,</span><span class="w"/><span class="n">ColorSpace</span><span class="p">.</span><span class="na">RGB</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">40</span><span class="w"/><span class="c1">// Encode the modified frame back into the video</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">41</span><span class="w"/><span class="n">encoder</span><span class="p">.</span><span class="na">encodeNativeFrame</span><span class="p">(</span><span class="n">pic</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">42</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">43</span><span class="w"/><span class="c1">// Finalize video encoding and clean up</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">44</span><span class="w"/><span class="n">NIOUtils</span><span class="p">.</span><span class="na">closeQuietly</span><span class="p">(</span><span class="n">fileChannelWrapperIN</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">45</span><span class="w"/><span class="c1">// Finalize and close the encoder (important!)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">46</span><span class="w"/><span class="n">encoder</span><span class="p">.</span><span class="na">finish</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">47</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">48</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">49</span><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toBinaryString</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">message</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">50</span><span class="w"/><span class="n">StringBuilder</span><span class="w"/><span class="n">binary</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">StringBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">51</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">char</span><span class="w"/><span class="n">character</span><span class="w"/><span class="p">:</span><span class="w"/><span class="n">message</span><span class="p">.</span><span class="na">toCharArray</span><span class="p">())</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">52</span><span class="w"/><span class="n">binary</span><span class="p">.</span><span class="na">append</span><span class="p">(</span><span class="n">String</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">53</span><span class="w"/><span class="p">.</span><span class="na">format</span><span class="p">(</span><span class="s">"%8s"</span><span class="p">,</span><span class="w"/><span class="n">Integer</span><span class="p">.</span><span class="na">toBinaryString</span><span class="p">(</span><span class="n">character</span><span class="p">))</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">54</span><span class="w"/><span class="p">.</span><span class="na">replace</span><span class="p">(</span><span class="sc">' '</span><span class="p">,</span><span class="w"/><span class="sc">'0'</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">55</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">56</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">binary</span><span class="p">.</span><span class="na">toString</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">57</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">58</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">59</span><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">BufferedImage</span><span class="w"/><span class="nf">encodeMessageOnBlue</span><span class="p">(</span><span class="n">BufferedImage</span><span class="w"/><span class="n">image</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">binaryMessage</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">60</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">messageIndex</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">61</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">messageLength</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">binaryMessage</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">62</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">63</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">y</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">y</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getHeight</span><span class="p">()</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">messageIndex</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">messageLength</span><span class="p">;</span><span class="w"/><span class="n">y</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">64</span><span class="w"/><span class="nf">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">x</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/><span class="n">x</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getWidth</span><span class="p">()</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">messageIndex</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">messageLength</span><span class="p">;</span><span class="w"/><span class="n">x</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">65</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">pixel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">getRGB</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="w"/><span class="n">y</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">66</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">alpha</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">pixel</span><span class="w"/><span class="o">&gt;&gt;</span><span class="w"/><span class="n">24</span><span class="p">)</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">67</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">red</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">pixel</span><span class="w"/><span class="o">&gt;&gt;</span><span class="w"/><span class="n">16</span><span class="p">)</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">68</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">green</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">pixel</span><span class="w"/><span class="o">&gt;&gt;</span><span class="w"/><span class="n">8</span><span class="p">)</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">69</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">blue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">pixel</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFF</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">70</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">71</span><span class="w"/><span class="c1">// Modify the LSB of the blue component</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">72</span><span class="w"/><span class="n">blue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">blue</span><span class="w"/><span class="o">&amp;</span><span class="w"/><span class="n">0xFE</span><span class="p">)</span><span class="w"/><span class="o">|</span><span class="w"/><span class="p">(</span><span class="n">binaryMessage</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">messageIndex</span><span class="p">)</span><span class="w"/><span class="o">-</span><span class="w"/><span class="sc">'0'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">73</span><span class="w"/><span class="n">messageIndex</span><span class="o">++</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">74</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">75</span><span class="w"/><span class="c1">// Reconstruct the pixel and set it back</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">76</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">newPixel</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">alpha</span><span class="w"/><span class="o">&lt;&lt;</span><span class="w"/><span class="n">24</span><span class="p">)</span><span class="w"/><span class="o">|</span><span class="w"/><span class="p">(</span><span class="n">red</span><span class="w"/><span class="o">&lt;&lt;</span><span class="w"/><span class="n">16</span><span class="p">)</span><span class="w"/><span class="o">|</span><span class="w"/><span class="p">(</span><span class="n">green</span><span class="w"/><span class="o">&lt;&lt;</span><span class="w"/><span class="n">8</span><span class="p">)</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">blue</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">77</span><span class="w"/><span class="n">image</span><span class="p">.</span><span class="na">setRGB</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="w"/><span class="n">y</span><span class="p">,</span><span class="w"/><span class="n">newPixel</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">78</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">79</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">80</span><span class="w"/><span class="k">return</span><span class="w"/><span class="n">image</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">81</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">82</span><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h3 id="file-steganography">File Steganography:</h3><p>Embedding files within other files, such as hiding a document within another document.</p><p>File steganography with PDF files involves hiding one PDF file within another PDF file. In this example, we&rsquo;ll hide the contents of one PDF file within the metadata of another PDF file. Specifically, we&rsquo;ll utilise the &ldquo;<strong>Keywords</strong> " metadata field of PDFs to embed the content of a secret PDF file.</p><p>Here&rsquo;s the Java code to achieve this using the Apache PDFBox library:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">9</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">10</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">HideMessageInPDF</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">11</span><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">12</span><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">13</span><span class="w"/><span class="c1">// Load an existing PDF document</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">14</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">file</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"_data/ReadME.pdf"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">15</span><span class="w"/><span class="n">PDDocument</span><span class="w"/><span class="n">document</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">PDDocument</span><span class="p">.</span><span class="na">load</span><span class="p">(</span><span class="n">file</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">16</span><span class="w"/><span class="c1">// Retrieve the document's metadata</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">17</span><span class="w"/><span class="n">PDDocumentInformation</span><span class="w"/><span class="n">info</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">document</span><span class="p">.</span><span class="na">getDocumentInformation</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">18</span><span class="w"/><span class="c1">// Add a hidden message to the metadata</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">19</span><span class="w"/><span class="c1">// You could use a less obvious key than "HiddenMessage" to make it less detectable</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">20</span><span class="w"/><span class="n">info</span><span class="p">.</span><span class="na">setCustomMetadataValue</span><span class="p">(</span><span class="s">"HiddenMessage"</span><span class="p">,</span><span class="w"/><span class="s">"This is a secret message"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">21</span><span class="w"/><span class="c1">// Save the modified document</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">22</span><span class="w"/><span class="n">document</span><span class="p">.</span><span class="na">save</span><span class="p">(</span><span class="s">"_data/ReadME_with-data.pdf"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">23</span><span class="w"/><span class="n">document</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">24</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Hidden message added to the PDF metadata."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">25</span><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">26</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">27</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">28</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">29</span><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">30</span><span class="w"/><span class="c1">// Load the PDF document</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">31</span><span class="w"/><span class="n">File</span><span class="w"/><span class="n">file</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">File</span><span class="p">(</span><span class="s">"_data/ReadME_with-data.pdf"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">32</span><span class="w"/><span class="n">PDDocument</span><span class="w"/><span class="n">document</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">PDDocument</span><span class="p">.</span><span class="na">load</span><span class="p">(</span><span class="n">file</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">33</span><span class="w"/><span class="c1">// Access the document's metadata</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">34</span><span class="w"/><span class="n">PDDocumentInformation</span><span class="w"/><span class="n">info</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">document</span><span class="p">.</span><span class="na">getDocumentInformation</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">35</span><span class="w"/><span class="c1">// Retrieve the hidden message from the metadata</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">36</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">hiddenMessage</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">info</span><span class="p">.</span><span class="na">getCustomMetadataValue</span><span class="p">(</span><span class="s">"HiddenMessage"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">37</span><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Hidden message: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">hiddenMessage</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">38</span><span class="w"/><span class="n">document</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">39</span><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">40</span><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">41</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">42</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">43</span><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>In this code:</p><p>1. The &ldquo;<strong>hidePDF</strong> " function loads the source PDF document, converts the content of the secret PDF file to a Base64 encoded string, and embeds it into the &ldquo;<strong>Keywords</strong> " metadata field of the source PDF file.</p><p>2. The &ldquo;<strong>extractPDF</strong> " function loads the steganographic PDF document, extracts the Base64 encoded content from the &ldquo;<strong>Keywords</strong> " metadata field, decodes it, and writes it to an output PDF file.</p><p>Make sure to replace every path to files with the appropriate file paths in your system. Additionally, you&rsquo;ll need to include the Apache PDFBox library in your project to use its functionalities.</p><h2 id="conclusion">Conclusion:</h2><p>As explored in this paper, steganography is a fascinating field with a rich history and diverse applications. By concealing information from ordinary data, steganography provides a powerful means of covert communication and data protection. Steganography continues to evolve alongside technological advancements, from ancient techniques of hiding messages in wax tablets to modern digital methods embedded within multimedia files.</p><p>While steganography offers numerous benefits in fields like digital watermarking, copyright protection, and secure communication, it also presents challenges and ethical considerations. Due to its subtle nature, detecting steganographic content remains a significant challenge, requiring advanced algorithms and tools for effective analysis.</p><p>As technology continues to advance, the importance of understanding steganography becomes increasingly critical. It underscores the need for robust security measures balanced with respect for privacy rights. By delving into the principles and techniques of steganography, researchers and practitioners can develop more effective strategies for exploiting and defending against covert communication methods.</p><p>In conclusion, steganography represents a dynamic and intriguing aspect of information security, with implications spanning various domains. By embracing its complexities and exploring its potential applications, we can navigate the intricate landscape of digital communication with greater insight and resilience.</p>
]]></content:encoded><category>Java</category><category>Security</category><media:content url="https://svenruppert.com/images/2024/03/IMG_0346.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/03/IMG_0346.jpg"/><enclosure url="https://svenruppert.com/images/2024/03/IMG_0346.jpg" type="image/jpeg" length="0"/></item><item><title>The Compensating Transaction Pattern</title><link>https://svenruppert.com/posts/the-compensating-transaction-pattern/</link><pubDate>Mon, 12 Feb 2024 12:40:41 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/the-compensating-transaction-pattern/</guid><description>The Bird-Eye View A Compensating Transaction Pattern is a technique used to ensure consistency when multiple steps are involved in a process, and some steps may fail. It essentially consists in having &ldquo;undo&rdquo; transactions for each successful step, so if something goes wrong later on, you can reverse the changes made earlier and maintain data integrity.</description><content:encoded>&lt;![CDATA[<h2 id="the-bird-eye-view">The Bird-Eye View</h2><p>A Compensating Transaction Pattern is a technique used to ensure consistency when multiple steps are involved in a process, and some steps may fail. It essentially consists in having &ldquo;undo&rdquo; transactions for each successful step, so if something goes wrong later on, you can reverse the changes made earlier and maintain data integrity.</p><p>Here&rsquo;s a breakdown of the key points:</p><h3 id="what-it-does">What it does:</h3><ul><li>Ensures consistency in eventually consistent operations with multiple steps.</li><li>Reverses the work done by previous successful steps if a later step fails.</li><li>Maintains data integrity by undoing changes in case of failure.</li></ul><h3 id="how-it-works">How it works:</h3><ul><li>Primary transaction: A series of steps are performed to complete a specific task.</li><li>Compensating transactions: A &ldquo;undo&rdquo; transaction is designed and stored for each successful step.</li><li>Failure: If a later step fails, the compensating transactions for the completed steps are executed reversely, effectively undoing the changes.</li></ul><ol><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#the-bird-eye-view">The Bird-Eye View</a><ol><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#what-it-does">What it does:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#how-it-works">How it works:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#benefits">Benefits:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#examples-of-use-cases">Examples of use cases:</a><ol><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#online-hotel-booking-system">Online Hotel Booking System:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#inventory-management-system">Inventory Management System:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#money-transfer-service">Money Transfer Service:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#flight-reservation-system">Flight Reservation System:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#order-processing-system-e-commerce">Order Processing System - (e-commerce):</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#tradeoffs">Tradeoffs:</a><ol><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#complexity">Complexity:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#concurrency-and-race-conditions">Concurrency and Race Conditions:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#performance-overhead">Performance Overhead:</a><ol><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#overhead-of-compensating-actions">Overhead of Compensating Actions:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#latency-and-response-time">Latency and Response Time:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#concurrency-and-scalability">Concurrency and Scalability:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#network-and-i-o-overhead">Network and I/O Overhead:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#transaction-retry-and-rollback">Transaction Retry and Rollback:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#resource-consumption">Resource Consumption:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#monitoring-and-logging-overhead">Monitoring and Logging Overhead:</a></li></ol></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#data-consistency">Data Consistency:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#error-handling-and-recovery">Error Handling and Recovery:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#auditability-and-monitoring">Auditability and Monitoring:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#development-and-testing-complexity">Development and Testing Complexity:</a><ol><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#compensating-action-design">Compensating Action Design:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#testing-failure-scenarios">Testing Failure Scenarios:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#concurrency-and-race-conditions-1">Concurrency and Race Conditions:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#error-handling-and-recovery-testing">Error Handling and Recovery Testing:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#integration-testing">Integration Testing:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#end-to-end-testing">End-to-End Testing:</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#unsuccessful-transaction-example-online-shopping-fail">Unsuccessful Transaction Example: Online Shopping Fail</a><ol><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#main-transaction">Main Transaction:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#compensating-transactions">Compensating Transactions:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#failure">Failure:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#compensating-actions">Compensating Actions:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#outcome">Outcome:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#additional-notes">Additional Notes:</a></li></ol></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#implementing-compensating-transactions-in-core-java-simplified-example">Implementing Compensating Transactions in Core Java (Simplified Example)</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#what-other-patterns-are-often-combined">What other Patterns are often combined?</a><ol><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#retry-pattern">Retry Pattern:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#saga-pattern">Saga Pattern:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#event-sourcing-pattern">Event Sourcing Pattern:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#circuit-breaker-pattern">Circuit Breaker Pattern:</a></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#idempotent-receiver-pattern">Idempotent Receiver Pattern:</a></li></ol></li><li><a href="https://svenruppert.com/2024/02/12/the-compensating-transaction-pattern/#conclusion">Conclusion:</a></li></ol><h3 id="benefits">Benefits:</h3><p>The Compensating Transaction Pattern offers several benefits in the context of distributed systems and complex transactions:</p><p><strong>Fault Tolerance</strong> : The pattern enhances fault tolerance by providing a mechanism to handle failures gracefully. When a failure occurs during a transaction, compensating actions can be executed to revert the system to a consistent state, ensuring that partial failures do not leave the system in an inconsistent or corrupted state.</p><p><strong>Consistency</strong> : Compensating transactions helps maintain data consistency across distributed systems by ensuring that changes made by failed transactions are properly reverted. This helps prevent data anomalies and ensures the system remains consistent, even during failures.</p><p><strong>Transaction Rollback</strong> : Unlike traditional transaction rollback mechanisms, compensating transactions offer more flexibility in handling complex transactions that involve multiple components or services. Instead of rolling back the entire transaction, compensating actions can selectively undo the effects of failed transactions, allowing other parts of the transaction to proceed unaffected.</p><p><strong>Resilience</strong> : By incorporating compensating transactions into the system design, developers can improve the resilience of distributed systems against various types of failures, including network issues, service unavailability, or system crashes. This resilience contributes to overall system stability and reliability.</p><p><strong>Flexibility</strong> : The pattern provides flexibility in designing and implementing transactional workflows in distributed systems. Developers can define compensating actions tailored to specific transactional requirements, allowing for custom handling of failures and recovery scenarios.</p><p><strong>Enhanced Scalability</strong> : Compensating transactions can facilitate the implementation of scalable distributed systems by decoupling transactional logic from individual components or services. This decoupling allows for better scalability and parallelisation of transaction processing across distributed environments.</p><p><strong>Improved Error Handling</strong> : Compensating transactions enables more robust error handling mechanisms by providing a structured approach to handling transaction failures. Developers can implement specific error-handling logic within compensating actions to address various failure scenarios, such as resource constraints or service timeouts.</p><p>Overall, the Compensating Transaction Pattern offers significant benefits in fault tolerance, consistency, resilience, flexibility, scalability, and error handling, making it a valuable pattern for designing and implementing distributed systems with complex transactional requirements.</p><h3 id="examples-of-use-cases">Examples of use cases:</h3><p>Here are five examples of the Compensating Transaction Pattern in action across various domains:</p><h4 id="online-hotel-booking-system">Online Hotel Booking System:</h4><p><strong>Main Transaction</strong> : Reserve a hotel room for a customer and charge their credit card.</p><p><strong>Compensating Actions</strong> : If charging the credit card fails or the reservation cannot be made due to a system error, release the reserved room and void the transaction authorisation on the credit card.</p><h4 id="inventory-management-system">Inventory Management System:</h4><p><strong>Main Transaction</strong> : Deduct inventory stock when an order is placed.</p><p><strong>Compensating Actions</strong> : If an order cannot be fulfilled due to insufficient stock or system errors, increment the stock count for the ordered items to revert the deduction.</p><h4 id="money-transfer-service">Money Transfer Service:</h4><p><strong>Main Transaction</strong> : Transfer funds from one account to another.</p><p><strong>Compensating Actions</strong> : If the transfer fails due to a network issue or insufficient funds, reverse the transfer by depositing the amount back into the sender&rsquo;s account.</p><h4 id="flight-reservation-system">Flight Reservation System:</h4><p><strong>Main Transaction</strong> : Book a flight ticket for a passenger and debit the payment from their account.</p><p><strong>Compensating Actions</strong> : If the payment fails or the reservation cannot be completed, cancel the booking and refund the payment to the passenger&rsquo;s account.</p><h4 id="order-processing-system---e-commerce">Order Processing System - (e-commerce):</h4><p><strong>Main Transaction</strong> : Process an order by updating inventory, charging the customer, and notifying the warehouse for shipment.</p><p><strong>Compensating Actions</strong> : If any step fails (e.g., payment authorisation fails, inventory update fails, or shipment notification fails), revert the changes made and inform the customer about the failure.</p><p>In each of these examples, the Compensating Transaction Pattern ensures that the system maintains consistency and integrity, even in the face of failures or errors during transaction processing. Compensating actions are designed to undo the effects of failed transactions and restore the system to a consistent state.</p><h2 id="tradeoffs">Tradeoffs:</h2><p>While the Compensating Transaction Pattern offers several benefits, it also comes with inevitable tradeoffs and considerations that developers need to take into account:</p><h3 id="complexity">Complexity:</h3><p>Implementing compensating transactions can add complexity to the system design and codebase. Developers must carefully design and manage compensating actions to ensure they accurately revert the effects of failed transactions. This complexity can make the codebase harder to understand, maintain, and debug.</p><h3 id="concurrency-and-race-conditions">Concurrency and Race Conditions:</h3><p>In distributed systems with concurrent transactions, coordinating compensating actions across multiple components or services can introduce race conditions and synchronisation challenges. Ensuring that compensating actions are executed correctly and idempotently in the presence of concurrent transactions requires careful attention to concurrency control mechanisms.</p><h3 id="performance-overhead">Performance Overhead:</h3><p>Executing compensating actions to revert failed transactions incurs additional processing overhead and resource utilisation. Depending on the complexity of compensating actions and the frequency of transaction failures, this overhead can impact system performance and scalability, particularly in high-throughput environments.</p><p>The performance implications of the Compensating Transaction Pattern depend on various factors, including the complexity of the transactional workflow, the frequency of transaction failures, the efficiency of compensating actions, and the underlying infrastructure of the distributed system. Here are some performance considerations associated with this pattern:</p><h4 id="overhead-of-compensating-actions">Overhead of Compensating Actions:</h4><p>Executing compensating actions to revert failed transactions incurs additional processing overhead and resource utilisation. Depending on the complexity of compensating actions, their execution may involve database updates, service calls, or other operations that consume CPU, memory, and network bandwidth.</p><h4 id="latency-and-response-time">Latency and Response Time:</h4><p>The time required to execute compensating actions impacts the overall latency and response time of transaction processing. If compensating actions involve time-consuming operations or depend on external services, they can introduce delays in recovering from transaction failures, affecting system responsiveness and user experience.</p><h4 id="concurrency-and-scalability">Concurrency and Scalability:</h4><p>Coordinating compensating actions across concurrent transactions can introduce contention and synchronisation overhead, impacting system scalability. In highly concurrent environments, ensuring that compensating actions are executed correctly and efficiently without causing bottlenecks or resource contention is crucial for maintaining performance under load.</p><h4 id="network-and-io-overhead">Network and I/O Overhead:</h4><p>Compensating actions involving remote services or accessing distributed data sources incur network and I/O overhead. Network latency, message serialisation/deserialisation, and network congestion can affect the performance of compensating transactions, particularly in geographically distributed systems.</p><h4 id="transaction-retry-and-rollback">Transaction Retry and Rollback:</h4><p>Implementing retry mechanisms for failed transactions and rolling back partially completed transactions involve additional computational and I/O overhead. Retrying failed transactions increases the workload on the system, primarily if retries are performed with exponential backoff or involve multiple retries before triggering compensating actions.</p><h4 id="resource-consumption">Resource Consumption:</h4><p>Compensating transactions may consume additional resources such as memory, disk space, and database connections. Managing resource usage and contention, especially in multi-tenant or shared infrastructure environments, is essential for maintaining overall system performance and stability.</p><h4 id="monitoring-and-logging-overhead">Monitoring and Logging Overhead:</h4><p>Capturing transactional events, logging execution outcomes, and monitoring the performance of compensating transactions impose overhead on system resources, particularly in environments with high transaction volumes. Balancing the granularity of monitoring and logging with performance considerations is essential for optimising system performance.</p><p>To mitigate the performance implications of the Compensating Transaction Pattern, developers can employ various strategies such as optimising compensating actions, reducing transactional complexity, implementing efficient retry mechanisms, optimising network communication, scaling infrastructure resources, and using caching and batching techniques. Additionally, conducting performance testing and profiling to identify and address performance bottlenecks is essential for optimising the performance of systems that utilise compensating transactions.</p><h3 id="data-consistency">Data Consistency:</h3><p>While compensating transactions helps maintain consistency in the face of transaction failures, ensuring data consistency across distributed systems remains a challenge. Coordinating compensating actions with other concurrent transactions and ensuring that data updates are properly synchronised can be complex, especially in systems with eventual consistency requirements.</p><p>Here&rsquo;s an example illustrating the potential data consistency problem:</p><p>Consider an e-commerce platform where customers can place orders for products. When a customer places an order, the system deducts the ordered items from the inventory and charges the customer&rsquo;s credit card. If either of these actions fails, compensating actions are executed to revert the transaction.</p><p>Let&rsquo;s suppose a customer orders a product, the inventory is successfully deducted, but the credit card charge fails due to a network issue. The compensating action would be adding the deducted items to the inventory. However, due to the system&rsquo;s distributed nature, there&rsquo;s a delay in executing the compensating action; in the meantime, another customer orders the same product.</p><p>If the compensating action to add back the deducted items is not synchronised or appropriately coordinated, it may lead to data inconsistency. Specifically, the inventory count may need to be corrected if the compensating action adds back the deducted items while another order for the same product is already processed. This can result in overselling products and accurate inventory counts, leading to customer satisfaction and operational inefficiencies.</p><p>To address this data consistency problem, developers need to implement proper synchronisation and coordination mechanisms to ensure that compensating actions are executed in a consistent and idempotent manner. This may involve using distributed transactions, locks, or other coordination techniques to coordinate compensating actions across distributed components and maintain data consistency. Additionally, implementing mechanisms to handle concurrent updates and conflicts, such as optimistic concurrency control or conflict resolution strategies, can help mitigate data consistency issues using the Compensating Transaction Pattern.</p><h3 id="error-handling-and-recovery">Error Handling and Recovery:</h3><p>Implementing robust error handling and recovery mechanisms for compensating transactions is essential but challenging. Developers need to anticipate and handle various failure scenarios, including partial failures, network issues, and service unavailability, to ensure that compensating actions are executed reliably and effectively.</p><h3 id="auditability-and-monitoring">Auditability and Monitoring:</h3><p>Tracking and auditing compensating transactions for accountability and compliance purposes can be challenging, especially in complex distributed systems with multiple transactional workflows. Implementing comprehensive monitoring and logging mechanisms to capture transactional events and execution outcomes is crucial for troubleshooting and compliance.</p><h3 id="development-and-testing-complexity">Development and Testing Complexity:</h3><p>Developing and testing compensating transactions require thoroughly validating the main transactional workflow and compensating actions. Testing failure scenarios and edge cases to ensure the correctness and robustness of compensating transactions can be time-consuming and resource-intensive.</p><p>Here&rsquo;s an example illustrating the complexities involved:</p><p>Consider a scenario where you&rsquo;re developing an online banking system that allows users to transfer funds between accounts. The system must deduct funds from the sender&rsquo;s account and credit them to the recipient&rsquo;s account. If the transfer fails (e.g., insufficient funds, network error), compensating actions must be executed to revert the transaction and restore the accounts to their original states.</p><p>Now, during development and testing of this system, you encounter several challenges related to the Compensating Transaction Pattern:</p><h4 id="compensating-action-design">Compensating Action Design:</h4><p>Designing compensating actions requires careful consideration of the actions needed to revert the effects of failed transactions. In the banking system example, you must define compensating actions to restore the sender&rsquo;s account balance if the transfer fails. This may involve adding back the deducted funds and updating transaction logs or audit trails.</p><h4 id="testing-failure-scenarios">Testing Failure Scenarios:</h4><p>Testing the system under various failure scenarios to ensure that compensating actions are triggered and executed correctly adds complexity to the testing process. For example, you must simulate scenarios such as insufficient funds, network timeouts, and service failures to verify that compensating actions are invoked and revert the transaction as expected.</p><h4 id="concurrency-and-race-conditions-1">Concurrency and Race Conditions:</h4><p>Testing concurrent transactions and ensuring that compensating actions are executed correctly in a concurrent environment requires thorough testing and validation. Race conditions and synchronisation issues may arise when multiple transactions are processed concurrently, leading to inconsistent or incorrect compensating action execution.</p><h4 id="error-handling-and-recovery-testing">Error Handling and Recovery Testing:</h4><p>Validating error handling and recovery mechanisms to ensure that the system gracefully handles failures and recovers consistently adds to the testing complexity. This includes testing retry mechanisms, rollback procedures, and error recovery paths to verify that the system behaves as expected under failure conditions.</p><h4 id="integration-testing">Integration Testing:</h4><p>Testing the integration of compensating transactions with other system components and services introduces additional complexity. To maintain data consistency and integrity, you need to ensure that compensating actions interact correctly with external dependencies, such as databases, messaging systems, and third-party services.</p><h4 id="end-to-end-testing">End-to-End Testing:</h4><p>To validate the entire transactional workflow, including main transactions and compensating actions, end-to-end testing requires comprehensive testing scenarios and test coverage. This involves testing different transaction sequences, error scenarios, and edge cases to verify the correctness and robustness of the system.</p><p>Overall, addressing the development and testing complexity associated with the Compensating Transaction Pattern requires thorough planning, design, and validation to ensure that compensating actions are correctly implemented, tested, and integrated into the system. Effective testing strategies, automation, and collaboration between development and testing teams are essential for mitigating the challenges and complexities of implementing this pattern.</p><p>In summary, while the Compensating Transaction Pattern offers benefits such as fault tolerance, consistency, and resilience, it also introduces complexity, concurrency challenges, performance overhead, and additional data consistency, error handling, auditability, and testing considerations. Understanding these tradeoffs and carefully designing compensating transactions is essential for effectively leveraging this pattern in distributed systems.</p><h2 id="unsuccessful-transaction-example-online-shopping-fail">Unsuccessful Transaction Example: Online Shopping Fail</h2><p>Imagine you&rsquo;re buying a new pair of shoes online. Here&rsquo;s an example of how a Compensating Transaction Pattern could be used in this scenario, and how it would work if the transaction failed:</p><p>Steps:</p><h3 id="main-transaction">Main Transaction:</h3><ul><li>You add the shoes to your cart and proceed to checkout.</li><li>You enter your billing and shipping information.</li><li>You authorise the payment for the shoes.</li><li>The store&rsquo;s inventory system updates, marking the shoes as &ldquo;sold.&rdquo;</li><li>The order confirmation email is sent to you.</li></ul><h3 id="compensating-transactions">Compensating Transactions:</h3><p>For each successful step:</p><ul><li>A compensating transaction is designed and stored.</li><li>For example, if needed, the inventory update would have a compensating transaction to reverse the &ldquo;sold&rdquo; status.</li></ul><h3 id="failure">Failure:</h3><p>Let&rsquo;s say the shipping address verification fails after successful payment authorisation. The main transaction cannot be completed.</p><h3 id="compensating-actions">Compensating Actions:</h3><ul><li>The compensating transaction for the inventory update is triggered.</li><li>The shoes are marked as &ldquo;available&rdquo; again.</li><li>The payment authorisation is reversed, and the funds are returned to your account.</li><li>You receive a notification about the failed transaction and the reason for failure.</li></ul><h3 id="outcome">Outcome:</h3><ul><li>While the purchase wasn&rsquo;t successful, the Compensating Transaction Pattern ensures data consistency.</li><li>Your money is safe, and the store&rsquo;s inventory remains accurate.</li></ul><h3 id="additional-notes">Additional Notes:</h3><ul><li>This is a simplified example, and real-world implementations can be more complex.</li><li>The specific compensating transactions depend on the particular systems and processes involved.</li></ul><h2 id="implementing-compensating-transactions-in-core-java-simplified-example">Implementing Compensating Transactions in Core Java (Simplified Example)</h2><p>Here&rsquo;s a simplified example in Core Java showcasing the essential aspects of the Compensating Transaction Pattern:</p><p><strong>Scenario</strong> : Booking a hotel room with payment.</p><p><strong>Classes</strong> :</p><ul><li>HotelBookingService: Maintains hotel booking logic.</li><li>PaymentService: Handles payment processing.</li><li>Booking: Represents a hotel room booking with details.</li></ul><p><strong>Code:</strong></p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">HotelBookingService</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="n">PaymentService</span><span class="w"/><span class="n">paymentService</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">HotelBookingService</span><span class="p">(</span><span class="n">PaymentService</span><span class="w"/><span class="n">paymentService</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">paymentService</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">paymentService</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">Booking</span><span class="w"/><span class="nf">bookRoom</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">guestName</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">roomType</span><span class="p">,</span><span class="w"/><span class="kt">double</span><span class="w"/><span class="n">price</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// 1. Book the room (potentially in a database)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Booking</span><span class="w"/><span class="n">booking</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Booking</span><span class="p">(</span><span class="n">guestName</span><span class="p">,</span><span class="w"/><span class="n">roomType</span><span class="p">,</span><span class="w"/><span class="n">price</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// ... (simulate database booking)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// 2. Process payment</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="n">paymentSuccess</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">paymentService</span><span class="p">.</span><span class="na">charge</span><span class="p">(</span><span class="n">price</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">paymentSuccess</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">booking</span><span class="p">;</span><span class="w"/><span class="c1">// success</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// 3. Compensate if payment fails:</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">cancelBooking</span><span class="p">(</span><span class="n">booking</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">throw</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">RuntimeException</span><span class="p">(</span><span class="s">"Payment failed, booking cancelled."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">cancelBooking</span><span class="p">(</span><span class="n">Booking</span><span class="w"/><span class="n">booking</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// ... (simulated database cancellation)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Booking cancelled for room: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">booking</span><span class="p">.</span><span class="na">getRoomType</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">PaymentService</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">charge</span><span class="p">(</span><span class="kt">double</span><span class="w"/><span class="n">amount</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// ... (simulated payment processing - can throw exceptions)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Processing payment of $"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">amount</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// For simplicity, always succeed in this example</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Booking</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">guestName</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">roomType</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">double</span><span class="w"/><span class="n">price</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// getters, setters, and constructors omitted for brevity</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p><strong>Explanation</strong> :</p><ul><li>“<strong>HotelBookingService.bookRoom</strong> ” initiates the main transaction:</li></ul><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="o">*</span><span class="w"/><span class="n">Books</span><span class="w"/><span class="n">the</span><span class="w"/><span class="nf">room</span><span class="w"/><span class="p">(</span><span class="n">simulated</span><span class="w"/><span class="n">with</span><span class="w"/><span class="n">a</span><span class="w"/><span class="n">comment</span><span class="p">).</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="o">*</span><span class="w"/><span class="n">Processes</span><span class="w"/><span class="n">payment</span><span class="w"/><span class="n">using</span><span class="w"/><span class="n">PaymentService</span><span class="p">.</span></span></span></code></pre></div></div><ul><li>If payment succeeds, the booked room is returned.</li><li>If payment<strong>fails</strong> :</li></ul><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="o">*</span><span class="w"/><span class="s">"**cancelBooking** "</span><span class="w"/><span class="n">is</span><span class="w"/><span class="n">called</span><span class="w"/><span class="n">to</span><span class="w"/><span class="n">undo</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">room</span><span class="w"/><span class="nf">booking</span><span class="w"/><span class="p">(</span><span class="n">simulated</span><span class="p">).</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="o">*</span><span class="w"/><span class="n">A</span><span class="w"/><span class="s">"**RuntimeException** "</span><span class="w"/><span class="n">is</span><span class="w"/><span class="n">thrown</span><span class="w"/><span class="n">to</span><span class="w"/><span class="n">indicate</span><span class="w"/><span class="n">failure</span><span class="p">.</span></span></span></code></pre></div></div><p><strong>Note</strong> :</p><ul><li>This is a simplified example for learning purposes and doesn&rsquo;t handle real-world complexities like concurrency, resource management, error handling, and persistence.</li><li>Payment service is always successful here for simplicity. In reality, it might fail, requiring more elaborate compensation logic.</li></ul><p>This is just a single example to illustrate the concepts. Actual implementations vary based on specific scenarios and technology stacks.</p><h2 id="what-other-patterns-are-often-combined">What other Patterns are often combined?</h2><p>The Compensating Transaction Pattern is often combined with other design patterns to create robust and flexible solutions for handling complex transactions and distributed systems. Some patterns that are frequently combined with the Compensating Transaction Pattern include:</p><h3 id="retry-pattern">Retry Pattern:</h3><p>This pattern involves retrying an failed operation, hoping it will succeed on subsequent attempts. Combining this pattern with compensating transactions can improve fault tolerance by automatically retrying the failed operation before executing compensating actions.</p><h3 id="saga-pattern">Saga Pattern:</h3><p>The Saga Pattern decomposes a long-running transaction into a series of smaller, localised transactions. Each step in the saga is executed atomically, and compensating transactions are defined to undo the effects of completed steps if a failure occurs. This pattern is highly compatible with compensating transactions and helps manage complex distributed transactions.</p><h3 id="event-sourcing-pattern">Event Sourcing Pattern:</h3><p>In event sourcing, the state of an application is determined by a sequence of events. This pattern can be combined with compensating transactions to ensure that events representing successful transactions are persisted only after the compensating actions for the entire transaction have been successfully executed.</p><h3 id="circuit-breaker-pattern">Circuit Breaker Pattern:</h3><p>The Circuit Breaker Pattern detects and prevents repeated failures when calling remote services. When combined with compensating transactions, the circuit breaker can temporarily stop sending requests to a failing service, allowing the system to execute compensating actions and recover from the failure.</p><h3 id="idempotent-receiver-pattern">Idempotent Receiver Pattern:</h3><p>This pattern ensures receiving systems can safely process the same message multiple times without causing unintended side effects. When combined with compensating transactions, idempotency guarantees that executing compensating actions numerous times does not lead to inconsistencies in the system.</p><p>By combining these patterns, developers can design resilient and scalable distributed systems that can effectively handle failures, maintain consistency, and recover from errors gracefully.</p><h2 id="conclusion">Conclusion:</h2><p>In conclusion, the Compensating Transaction Pattern offers valuable benefits such as fault tolerance, data consistency, and resilience in distributed systems by providing a mechanism to handle transaction failures and maintain system integrity. However, leveraging this pattern effectively requires careful consideration of its tradeoffs and complexities.</p><p>While compensating transactions enhance fault tolerance and data consistency, they introduce development and testing complexity due to the intricacies of designing and validating compensating actions and ensuring the correctness and robustness of transactional workflows. Challenges such as concurrency, error handling, integration, and end-to-end testing require thorough planning, design, and validation to mitigate.</p><p>Despite these challenges, the Compensating Transaction Pattern remains a powerful tool for building resilient and scalable distributed systems. By addressing the complexities and tradeoffs associated with this pattern through effective design, testing, and validation strategies, developers can harness its benefits to build reliable and fault-tolerant systems that meet the demands of modern distributed computing environments.</p>
]]></content:encoded><category>Design Pattern</category><category>Java</category><media:content url="https://svenruppert.com/images/2024/02/DSC1098_landscape-crop.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/02/DSC1098_landscape-crop.jpeg"/><enclosure url="https://svenruppert.com/images/2024/02/DSC1098_landscape-crop.jpeg" type="image/jpeg" length="0"/></item><item><title>Serialising in Java - Birds Eye View</title><link>https://svenruppert.com/posts/serialising-in-java-birds-eye-view/</link><pubDate>Sun, 11 Feb 2024 13:46:53 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/serialising-in-java-birds-eye-view/</guid><description>Serialisation in Java is implemented to convert the state of an object into a byte stream, which can be quickly persisted to a file or sent over a network. This process is essential for persisting object data, supporting network communication, and facilitating sharing of objects between different parts of a distributed system.</description><content:encoded>&lt;![CDATA[<p>Serialisation in Java is implemented to convert the state of an object into a byte stream, which can be quickly persisted to a file or sent over a network. This process is essential for persisting object data, supporting network communication, and facilitating sharing of objects between different parts of a distributed system.</p><h3 id="basics-of-serialization">Basics of Serialization:</h3><h4 id="object-serialization">Object Serialization:</h4><p>- Serialization is converting an object into a sequence of bytes.</p><p>- The serialised form of an object can be saved to a file or sent over a network.</p><p>- Deserialization is the process of reconstructing the object from its serialised form.</p><h4 id="serializable-interface">Serializable Interface:</h4><p>- In Java, the<code>java.io.Serializable</code> interface makes a class serialisable.</p><p>- This interface acts as a marker interface, indicating that class objects can be serialised.</p><h4 id="transient-keyword">Transient Keyword:</h4><p>- The<code>transient</code> keyword can mark instance variables that should not be serialised.</p><p>- Transient variables are not included in the serialised form when an object is serialised.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">MyClass</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">normalVar</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">transient</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">transientVar</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="the-serialisation-process">The Serialisation Process:</h3><h4 id="objectoutputinputstream">ObjectOutput/InputStream:</h4><p>- The<code>ObjectOutputStream</code> class is used for serialising objects.</p><p>- The<code>ObjectInputStream</code> class is used for deserialising objects.</p><h4 id="serialisable-fields">Serialisable Fields:</h4><p>- Only the serialisation process will include the fields of a class marked as serialisable (i.e., non-transient and their types are serialisable).</p><h4 id="serialisable-methods">Serialisable Methods:</h4><p>- If a class provides its own<code>writeObject</code> and<code>readObject</code> methods, these methods will be responsible for the custom serialisation and deserialisation logic.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">writeObject</span><span class="p">(</span><span class="n">ObjectOutputStream</span><span class="w"/><span class="n">out</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Custom serialisation logic</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">readObject</span><span class="p">(</span><span class="n">ObjectInputStream</span><span class="w"/><span class="n">in</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="p">,</span><span class="w"/><span class="n">ClassNotFoundException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Custom deserialisation logic</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h3 id="serialisation-control">Serialisation Control:</h3><h4 id="serialversionuid">SerialVersionUID:</h4><p>- Every serialisable class has a version number, known as<code>serialVersionUID</code>.</p><p>- It is used during deserialisation to verify that the sender and receiver of a serialised object have loaded classes for that object that are compatible concerning serialisation.</p><h4 id="externalisable-interface">Externalisable Interface:</h4><p>In Java, the<code>**Externalizable**</code> interface provides a way to customise an object&rsquo;s serialisation and deserialisation process. Unlike the<code>Serializable</code> interface, which performs automatic serialisation,<code>**Externalizable**</code> allows you more control over the process by implementing two methods:<code>**writeExternal**</code> and<code>**readExternal**</code>.</p><p>Here&rsquo;s a simple example to demonstrate the use of<code>Externalizable</code>:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">Person</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Externalizable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">age</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Required public no-argument constructor</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">Person</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">Person</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">age</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">age</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">age</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">writeExternal</span><span class="p">(</span><span class="n">ObjectOutput</span><span class="w"/><span class="n">out</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Custom serialization logic</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">out</span><span class="p">.</span><span class="na">writeObject</span><span class="p">(</span><span class="n">name</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">out</span><span class="p">.</span><span class="na">writeInt</span><span class="p">(</span><span class="n">age</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">readExternal</span><span class="p">(</span><span class="n">ObjectInput</span><span class="w"/><span class="n">in</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="p">,</span><span class="w"/><span class="n">ClassNotFoundException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Custom deserialization logic</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="p">)</span><span class="w"/><span class="n">in</span><span class="p">.</span><span class="na">readObject</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">age</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">in</span><span class="p">.</span><span class="na">readInt</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Person{name='"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">name</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"', age="</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">age</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"}"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ExternalizableExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a Person object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">person</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Person</span><span class="p">(</span><span class="s">"John"</span><span class="p">,</span><span class="w"/><span class="n">30</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Serialize the object to a file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">ObjectOutputStream</span><span class="w"/><span class="n">oos</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectOutputStream</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">FileOutputStream</span><span class="p">(</span><span class="s">"person.ser"</span><span class="p">)))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">oos</span><span class="p">.</span><span class="na">writeObject</span><span class="p">(</span><span class="n">person</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Deserialize the object from the file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">deserializedPerson</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">ObjectInputStream</span><span class="w"/><span class="n">ois</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectInputStream</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="s">"person.ser"</span><span class="p">)))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">deserializedPerson</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">Person</span><span class="p">)</span><span class="w"/><span class="n">ois</span><span class="p">.</span><span class="na">readObject</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">ClassNotFoundException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Print the deserialized object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">deserializedPerson</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="kc">null</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Deserialized Person: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">deserializedPerson</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the<code>Person</code> class implements the<code>Externalizable</code> interface and provides custom serialisation and deserialisation logic in the<code>**writeExternal**</code> and<code>**readExternal**</code> methods. The<code>main</code> method demonstrates how to create a<code>Person</code> object, serialise it to a file, and then deserialise it back into a new object.</p><h4 id="why-should-i-use-externalizable">Why should I use Externalizable?</h4><p>The<code>**Externalizable**</code> interface in Java provides a more flexible and customised approach to serialisation compared to the default serialisation mechanism provided by the<code>Serializable</code> interface. Here are some reasons you might want to use<code>Externalizable</code>:</p><h5 id="selective-serialization-"><strong>Selective Serialization</strong> :</h5><p>With<code>**Externalizable**</code>, you have complete control over which fields get serialised and how they are serialised. This can be useful when you exclude certain fields from serialisation or perform custom serialisation for specific fields.</p><h5 id="performance-optimization">Performance Optimization:</h5><p><code>**Externalizable**</code> allows you to implement custom serialisation logic that might be more efficient than the default serialisation mechanism. You can choose to serialise only essential data, avoiding unnecessary information and potentially reducing the size of the serialised data.</p><h5 id="versioning-control">Versioning Control:</h5><p>When your class evolves and you need to maintain backwards or forward compatibility,<code>Externalizable</code> provides a way to handle versioning explicitly. You can implement custom logic to manage different versions of the serialised data, making it more adaptable to changes in your class structure.</p><h5 id="security-considerations">Security Considerations:</h5><p>Custom serialisation logic can help handle security-related concerns. For example, encrypt sensitive information before serialisation or perform other security checks during the serialisation/deserialisation process.</p><h5 id="resource-management">Resource Management:</h5><p>With<code>**Externalizable**</code>, you control resource management during serialisation and deserialisation. You can explicitly release and acquire resources as needed, which can be crucial when resources must be managed carefully.</p><h5 id="reducing-serialized-size">Reducing Serialized Size:</h5><p>If your class has transient or derived fields that need not be serialised, you can exclude them from the serialised form, leading to a smaller serialised size.</p><p>It&rsquo;s worth noting that while<code>**Externalizable**</code> provides more control, it also requires more manual effort to implement, as you need to write custom serialisation and deserialisation logic. In many cases, the default serialisation provided by<code>Serializable</code> is sufficient and more straightforward. However,<code>**Externalizable**</code> offers a powerful alternative for scenarios where customisation is necessary.</p><h3 id="summary-"><strong>Summary</strong> :</h3><p>Serialisation is a fundamental concept in Java that facilitates the storage and transmission of object data. It provides a versatile mechanism for persisting object states, supporting network communication, and enabling interoperability between distributed system components. While the default serialisation mechanism is suitable for many cases, understanding advanced topics like custom serialisation, versioning, and security considerations is essential for developing robust and efficient Java applications.</p><h2 id="usecases-for-serialisation-in-java">UseCases for Serialisation in Java</h2><p>The main reasons for implementing serialisation in Java are as follows:</p><h3 id="persistence-"><strong>Persistence</strong> :</h3><p>Serialisation allows objects to be saved to a file and later restored. This is useful for applications that need to store and retrieve the state of objects, such as in database interactions or for caching purposes.</p><h4 id="persistence---example">Persistence - Example:</h4><p>Let&rsquo;s consider a simple example where we have a<code>Person</code> class that we want to persist to a file using serialisation.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">Person</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">age</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">Person</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">age</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">age</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">age</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Person{name='"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">name</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"', age="</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">age</span><span class="w"/><span class="o">+</span><span class="w"/><span class="sc">'}'</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">SerializationExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a Person object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">person</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Person</span><span class="p">(</span><span class="s">"John Doe"</span><span class="p">,</span><span class="w"/><span class="n">25</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Serialize the object to a file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">serializePerson</span><span class="p">(</span><span class="n">person</span><span class="p">,</span><span class="w"/><span class="s">"person.ser"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Deserialize the object from the file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">deserializedPerson</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">deserializePerson</span><span class="p">(</span><span class="s">"person.ser"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Display the deserialised object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Deserialized Person: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">deserializedPerson</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">serializePerson</span><span class="p">(</span><span class="n">Person</span><span class="w"/><span class="n">person</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">ObjectOutputStream</span><span class="w"/><span class="n">outputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectOutputStream</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">FileOutputStream</span><span class="p">(</span><span class="n">fileName</span><span class="p">)))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Write the object to the file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">outputStream</span><span class="p">.</span><span class="na">writeObject</span><span class="p">(</span><span class="n">person</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Serialization successful. Object saved to "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Person</span><span class="w"/><span class="nf">deserializePerson</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">fileName</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">person</span><span class="w"/><span class="o">=</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">ObjectInputStream</span><span class="w"/><span class="n">inputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectInputStream</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="n">fileName</span><span class="p">)))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Read the object from the file</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">person</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">Person</span><span class="p">)</span><span class="w"/><span class="n">inputStream</span><span class="p">.</span><span class="na">readObject</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Deserialization successful. Object loaded from "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">fileName</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">ClassNotFoundException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">person</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the<code>Person</code> class implements the<code>Serializable</code> interface, indicating that its instances can be serialised. The<code>SerializationExample</code> class demonstrates how to serialise a<code>Person</code> object to a file (<code>person.ser</code>) and then deserialise it back into a new<code>Person</code> object. The<code>serializePerson</code> and<code>deserializePerson</code> methods handle the serialisation and deserialisation processes, respectively.</p><h3 id="network-communication-"><strong>Network Communication</strong> :</h3><p>Serialisation enables the easy transmission of objects between different Java Virtual Machines (JVMs) over a network. Converting objects to a byte stream can be sent across a network and reconstructed on the receiving end.</p><h4 id="network-communication---example">Network Communication - Example:</h4><p>In this example, we&rsquo;ll create a simple client-server communication scenario using Java sockets and serialisation. The server will send a serialised object to the client over the network.</p><p>Let&rsquo;s start with the server:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.ServerSocket</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.Socket</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">Person</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">age</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">Person</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">age</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">age</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">age</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Person{name='"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">name</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"', age="</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">age</span><span class="w"/><span class="o">+</span><span class="w"/><span class="sc">'}'</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Server</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a server socket</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ServerSocket</span><span class="w"/><span class="n">serverSocket</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ServerSocket</span><span class="p">(</span><span class="n">12345</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Server listening on port 12345..."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">while</span><span class="w"/><span class="p">(</span><span class="kc">true</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Wait for a client to connect</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Socket</span><span class="w"/><span class="n">clientSocket</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serverSocket</span><span class="p">.</span><span class="na">accept</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Client connected: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">clientSocket</span><span class="p">.</span><span class="na">getInetAddress</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a Person object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">person</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Person</span><span class="p">(</span><span class="s">"Alice"</span><span class="p">,</span><span class="w"/><span class="n">30</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Serialize and send the object to the client</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">sendObjectToClient</span><span class="p">(</span><span class="n">person</span><span class="p">,</span><span class="w"/><span class="n">clientSocket</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Close the client socket</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">clientSocket</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">sendObjectToClient</span><span class="p">(</span><span class="n">Serializable</span><span class="w"/><span class="n">obj</span><span class="p">,</span><span class="w"/><span class="n">Socket</span><span class="w"/><span class="n">socket</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">ObjectOutputStream</span><span class="w"/><span class="n">outputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectOutputStream</span><span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="na">getOutputStream</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Write the object to the output stream</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">outputStream</span><span class="p">.</span><span class="na">writeObject</span><span class="p">(</span><span class="n">obj</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Object sent to client."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>And here&rsquo;s the client:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.net.Socket</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Client</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Connect to the server</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Socket</span><span class="w"/><span class="n">socket</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Socket</span><span class="p">(</span><span class="s">"localhost"</span><span class="p">,</span><span class="w"/><span class="n">12345</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Receive the serialised object from the server</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">receivedPerson</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">receiveObjectFromServer</span><span class="p">(</span><span class="n">socket</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Display the received object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Received Person from server: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">receivedPerson</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Close the socket</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">socket</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">ClassNotFoundException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Person</span><span class="w"/><span class="nf">receiveObjectFromServer</span><span class="p">(</span><span class="n">Socket</span><span class="w"/><span class="n">socket</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="p">,</span><span class="w"/><span class="n">ClassNotFoundException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">ObjectInputStream</span><span class="w"/><span class="n">inputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectInputStream</span><span class="p">(</span><span class="n">socket</span><span class="p">.</span><span class="na">getInputStream</span><span class="p">()))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Read the object from the input stream</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="p">(</span><span class="n">Person</span><span class="p">)</span><span class="w"/><span class="n">inputStream</span><span class="p">.</span><span class="na">readObject</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the<code>Server</code> class listens for incoming connections on port 12345. When a client connects, it creates a<code>Person</code> object, serialises it, and sends it to the client using the<code>sendObjectToClient</code> method. The<code>Client</code> class connects to the server, receives the serialised<code>Person</code> object, and then deserialises and prints it.</p><h3 id="object-cloning-"><strong>Object Cloning</strong> :</h3><p>Serialisation provides a way to create a deep copy of an object. By serialising an object and then deserialising it, you effectively create a new copy of the original object. This can be useful when you need to duplicate complex object structures.</p><h4 id="object-cloning---example">Object Cloning - Example:</h4><p>In this example, I&rsquo;ll demonstrate object cloning using serialisation in Java. We&rsquo;ll create a<code>Person</code> class and use serialisation and deserialisation to perform deep cloning.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">Address</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">street</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">city</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">Address</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">street</span><span class="p">,</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">city</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">street</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">street</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">city</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">city</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Address{street='"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">street</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"', city='"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">city</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"'}"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">class</span><span class="nc">Person</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">age</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">Address</span><span class="w"/><span class="n">address</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">Person</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">age</span><span class="p">,</span><span class="w"/><span class="n">Address</span><span class="w"/><span class="n">address</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">age</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">age</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">address</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">address</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Person{name='"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">name</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"', age="</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">age</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">", address="</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">address</span><span class="w"/><span class="o">+</span><span class="w"/><span class="sc">'}'</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ObjectCloningExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Create a Person object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Address</span><span class="w"/><span class="n">originalAddress</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Address</span><span class="p">(</span><span class="s">"123 Main St"</span><span class="p">,</span><span class="w"/><span class="s">"Cityville"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">originalPerson</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Person</span><span class="p">(</span><span class="s">"John Doe"</span><span class="p">,</span><span class="w"/><span class="n">25</span><span class="p">,</span><span class="w"/><span class="n">originalAddress</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Clone the object using serialisation and deserialisation</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">clonedPerson</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">cloneObject</span><span class="p">(</span><span class="n">originalPerson</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Modify the original object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">originalPerson</span><span class="p">.</span><span class="na">setName</span><span class="p">(</span><span class="s">"Jane Doe"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">originalPerson</span><span class="p">.</span><span class="na">setAge</span><span class="p">(</span><span class="n">30</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">originalAddress</span><span class="p">.</span><span class="na">setStreet</span><span class="p">(</span><span class="s">"456 Oak St"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Display the original and cloned objects</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Original Person: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">originalPerson</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Cloned Person: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">clonedPerson</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Person</span><span class="w"/><span class="nf">cloneObject</span><span class="p">(</span><span class="n">Person</span><span class="w"/><span class="n">original</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Serialize the original object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ByteArrayOutputStream</span><span class="w"/><span class="n">bos</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ByteArrayOutputStream</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ObjectOutputStream</span><span class="w"/><span class="n">out</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectOutputStream</span><span class="p">(</span><span class="n">bos</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">out</span><span class="p">.</span><span class="na">writeObject</span><span class="p">(</span><span class="n">original</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">out</span><span class="p">.</span><span class="na">flush</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Deserialize the object to create a deep copy</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ByteArrayInputStream</span><span class="w"/><span class="n">bis</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ByteArrayInputStream</span><span class="p">(</span><span class="n">bos</span><span class="p">.</span><span class="na">toByteArray</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ObjectInputStream</span><span class="w"/><span class="n">in</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectInputStream</span><span class="p">(</span><span class="n">bis</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="p">(</span><span class="n">Person</span><span class="p">)</span><span class="w"/><span class="n">in</span><span class="p">.</span><span class="na">readObject</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">ClassNotFoundException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The<code>Person</code> class contains an<code>Address</code> object in this example. The<code>ObjectCloningExample</code> class demonstrates how to clone a<code>Person</code> object by serialising it to a byte stream and then deserialising it to create a deep copy. The<code>cloneObject</code> method handles the serialisation and deserialisation process. After cloning, modifications to the original object do not affect the cloned object, demonstrating a deep copy.</p><h3 id="remote-method-invocation-rmi-"><strong>Remote Method Invocation (RMI)</strong> :</h3><p>Java Remote Method Invocation (RMI) is a mechanism that allows objects to invoke methods on objects in another JVM. Serialisation is used to marshal and unmarshal parameters and return values during remote method calls.</p><h3 id="javabeans-"><strong>JavaBeans</strong> :</h3><p>Serialisation is commonly used in JavaBeans, reusable software components for Java. JavaBeans often need to be serialised to be easily stored or transmitted.</p><h3 id="heads-up">Heads Up</h3><p>Despite these advantages, it&rsquo;s important to note that not all classes can or should be serialised. For a class to be serialisable, it must implement the<code>Serializable</code> interface. Additionally, care should be taken when serialising objects to ensure security and proper handling of changes to class definitions over time (versioning).</p><h2 id="what-are-the-security-issues">What are the security issues?</h2><p>Java Serialization can introduce security issues, particularly when deserialising data from untrusted sources. Here are some of the security concerns associated with Java Serialization:</p><h3 id="remote-code-execution">Remote Code Execution:</h3><p>One of the most significant security risks with Java Serialization is the potential for remote code execution. When deserialising an object, the Java runtime system can execute arbitrary code within the serialised data. Attackers can exploit this to execute malicious code on the target system. This vulnerability can lead to serious security breaches.</p><h3 id="denial-of-service-dos">Denial of Service (DoS):</h3><p>An attacker can create a serialised object with a large size, causing excessive memory consumption and potentially leading to a denial of service attack. Deserialising large objects can consume significant CPU and memory resources, slowing down or crashing the application.</p><h3 id="data-tampering">Data Tampering:</h3><p>Serialised data can be tampered with during transmission or storage. Attackers can modify the serialised byte stream to alter the state of the deserialised object or introduce vulnerabilities.</p><h3 id="insecure-deserialization">Insecure Deserialization:</h3><p>Deserialising untrusted data without proper validation can lead to security issues. For example, if a class that performs sensitive operations is deserialised from untrusted input, an attacker can manipulate the object&rsquo;s state to perform unauthorised actions.</p><h3 id="information-disclosure">Information Disclosure:</h3><p>When objects are serialised, sensitive information may be included in the serialised form. An attacker may gain access to sensitive information if this data needs to be adequately protected or encrypted.</p><h2 id="how-to-mitigate-serialization-issues">How to Mitigate Serialization Issues</h2><p>To mitigate these security issues, consider the following best practices:</p><h3 id="avoid-deserializing-untrusted-data">Avoid Deserializing Untrusted Data:</h3><p>Avoid deserialising data from untrusted sources altogether. Instead, use safer data interchange formats like JSON or XML for untrusted data.</p><h3 id="implement-input-validation">Implement Input Validation:</h3><p>When deserialising data, validate and sanitise the input to ensure it adheres to expected data structures and doesn&rsquo;t contain unexpected or malicious data.</p><h3 id="use-security-managers">Use Security Managers:</h3><p>Java&rsquo;s Security Manager can be used to restrict the permissions and actions of deserialised code. However, it&rsquo;s important to note that security managers have been removed in newer versions of Java.</p><h3 id="whitelist-classes">Whitelist Classes:</h3><p>Limit the classes that can be deserialised to a predefined set of trusted classes. This can help prevent the deserialisation of arbitrary and potentially malicious classes.</p><h3 id="versioning-and-compatibility">Versioning and Compatibility:</h3><p>Be cautious when making changes to serialised classes. Use<code>serialVersionUID</code> to manage versioning and compatibility between different versions of serialised objects.</p><h4 id="what-is-serialversionuid-in-java-and-how-does-it-work">What is serialVersionUID in Java, and how does it work?</h4><p>In Java, the<code>serialVersionUID</code> is a unique identifier associated with a serialisable class. It is used during object serialisation to verify that the sender and receiver of a serialised object have loaded classes for that object that are compatible with serialisation. If the receiver has loaded a class for the object with a different<code>serialVersionUID</code> than the corresponding class on the sender side, deserialisation will result in an<code>InvalidClassException</code>.</p><p>Here&rsquo;s how it works:</p><h5 id="automatic-serialization">Automatic Serialization:</h5><p>When you mark a class as<code>Serializable</code> in Java, the serialisation mechanism automatically generates a serialVersionUID for that class unless you provide one explicitly. This autogenerated<code>serialVersionUID</code> is based on various aspects of the class, including its name, implemented interfaces, fields, and methods.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.Serializable</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MyClass</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// class code</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h5 id="explicit-serialversionuid">Explicit serialVersionUID:</h5><p>You can also explicitly declare a<code>serialVersionUID</code> in your class to have more control over versioning. This can be useful to avoid unexpected serialisation compatibility issues when the class structure changes.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.Serializable</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MyClass</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">serialVersionUID</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">123456789L</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// class code</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>It&rsquo;s important to note that if you don&rsquo;t provide an explicit<code>serialVersionUID</code>, the Java runtime will automatically generate one based on the class&rsquo;s structure, and changes to the class may result in a different autogenerated ID.</p><h5 id="versioning">Versioning:</h5><p>When an object is serialised, the<code>serialVersionUID</code> is also stored in the serialised form. During deserialisation, the receiving end compares the<code>serialVersionUID</code> of the loaded class with the one stored in the serialised data. If they match, deserialisation proceeds; otherwise, an<code>InvalidClassException</code> is thrown.</p><p>This mechanism helps ensure that the serialised data is compatible with the current class version. If you make changes to the class that are not backwards-compatible, you should update the<code>serialVersionUID</code> to indicate that the new class is incompatible with the previous version.</p><p>In summary,<code>serialVersionUID</code> in Java is a version control mechanism for serialised objects, and it plays a crucial role in maintaining compatibility between different versions of a class during object serialisation and deserialisation.</p><h4 id="serialversionuid-example">SerialVersionUID Example</h4><p>Imagine you have a<code>Person</code> class that represents a person with a name and an age, and you want to serialise instances of this class.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.io.*</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Person</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">serialVersionUID</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">1L</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">age</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">Person</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">name</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">age</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">name</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">name</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">age</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">age</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// getters and setters</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"Person{name='"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">name</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">"', age="</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">age</span><span class="w"/><span class="o">+</span><span class="w"/><span class="sc">'}'</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Serialization</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">serializePerson</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Deserialization</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">deserializedPerson</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">deserializePerson</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Deserialized Person: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">deserializedPerson</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">serializePerson</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">ObjectOutputStream</span><span class="w"/><span class="n">oos</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectOutputStream</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">FileOutputStream</span><span class="p">(</span><span class="s">"person.ser"</span><span class="p">)))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Person</span><span class="w"/><span class="n">person</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Person</span><span class="p">(</span><span class="s">"John"</span><span class="p">,</span><span class="w"/><span class="n">25</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">oos</span><span class="p">.</span><span class="na">writeObject</span><span class="p">(</span><span class="n">person</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Person serialized successfully."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="n">Person</span><span class="w"/><span class="nf">deserializePerson</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">ObjectInputStream</span><span class="w"/><span class="n">ois</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectInputStream</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="s">"person.ser"</span><span class="p">)))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="p">(</span><span class="n">Person</span><span class="p">)</span><span class="w"/><span class="n">ois</span><span class="p">.</span><span class="na">readObject</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">ClassNotFoundException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">null</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example:</p><p>- The<code>Person</code> class implements the<code>Serializable</code> interface.</p><p>- It has a<code>serialVersionUID</code> field explicitly set to<code>1L</code>.</p><p>- The<code>serializePerson</code> method creates a<code>Person</code> object, serialises it, and writes it to a file called<code>person.ser</code>.</p><p>- The<code>deserializePerson</code> method reads and returns the serialised<code>Person</code> object from the file.</p><p>Now, let&rsquo;s see how versioning works:</p><p>1. Run the program to serialise a<code>Person</code> object and write it to the file.</p><p>2. Change the<code>Person</code> class by adding a new field, for example,<code>private boolean isStudent;</code>.</p><p>3. rerun the program to deserialise the<code>Person</code> object.</p><p>If you don&rsquo;t update the<code>serialVersionUID</code> when you add the new field, you will likely encounter an<code>InvalidClassException</code> during deserialisation. To avoid this, you should update the<code>serialVersionUID</code> to indicate that the new version of the class is not compatible with the previous one:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="o">**</span><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kt">long</span><span class="w"/><span class="n">serialVersionUID</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">2L</span><span class="p">;</span><span class="o">**</span></span></span></code></pre></div></div><p>When you rerun the program, it should deserialise the object successfully, and the<code>isStudent</code> field will have its default value (<code>false</code>). Remember that this is a simple example; in a real-world scenario, you might need to implement more sophisticated versioning strategies based on your application&rsquo;s requirements.</p><h3 id="security-libraries">Security Libraries:</h3><p>Consider using third-party libraries like Apache Commons Collections or OWASP Java Serialization Security (Java-Serial-Killer) to help mitigate known vulnerabilities and prevent common attacks.</p><h3 id="lessons-learned">Lessons Learned</h3><p>In summary, Java Serialization can introduce serious security risks, especially when dealing with untrusted data. It&rsquo;s essential to take precautions, validate inputs, and consider alternative serialisation methods or libraries to enhance security. Additionally, keeping your Java runtime environment up to date is crucial, as newer versions of Java may include security improvements and fixes for known vulnerabilities.</p><h2 id="conclusion">Conclusion:</h2><p>Overall, serialisation is both a powerful and dangerous tool. We must balance usage and comfort at each location against our security needs. If you look at open-source projects, you can see a lot of activity to minimise the risk and increase the abstraction of this topic for everyday use.</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Security</category><media:content url="https://svenruppert.com/images/2023/12/DSC1101-v001-landscape-crop.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2023/12/DSC1101-v001-landscape-crop.jpg"/><enclosure url="https://svenruppert.com/images/2023/12/DSC1101-v001-landscape-crop.jpg" type="image/jpeg" length="0"/></item><item><title>Contextual Analysis in Cybersecurity</title><link>https://svenruppert.com/posts/contextual-analysis-in-cybersecurity/</link><pubDate>Mon, 05 Feb 2024 17:49:29 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/contextual-analysis-in-cybersecurity/</guid><description>Contextual analysis in cybersecurity involves examining events, actions, or data within the broader context of an organization&rsquo;s IT environment. It is a critical component of a proactive cybersecurity strategy, aiming to understand the significance of activities by considering various factors surrounding them. This multifaceted approach helps cybersecurity professionals identify and respond to potential threats effectively.</description><content:encoded>&lt;![CDATA[<p>Contextual analysis in cybersecurity involves examining events, actions, or data within the broader context of an organization&rsquo;s IT environment. It is a critical component of a proactive cybersecurity strategy, aiming to understand the significance of activities by considering various factors surrounding them. This multifaceted approach helps cybersecurity professionals identify and respond to potential threats effectively.</p><ol><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#key-concepts">Key Concepts</a><ol><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#situational-awareness">Situational Awareness</a><ol><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#normal-scenario">Normal Scenario:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#anomalous-scenario">Anomalous Scenario:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#situational-awareness-analysis">Situational Awareness Analysis:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#potential-explanations">Potential Explanations:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#response">Response:</a></li></ol></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#data-correlation">Data Correlation</a><ol><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#scenario">Scenario:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#data-sources">Data Sources:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#correlation">Correlation:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#correlated-analysis">Correlated Analysis:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#response">Response:</a></li></ol></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#threat-intelligence-integration">Threat Intelligence Integration</a><ol><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#scenario">Scenario:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#threat-intelligence-integration">Threat Intelligence Integration:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#response">Response:</a></li></ol></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#user-behavior-analysis">User Behavior Analysis</a><ol><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#scenario">Scenario:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#user-behavior-analysis">User Behavior Analysis:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#response">Response:</a></li></ol></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#temporal-analysis">Temporal Analysis</a><ol><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#scenario">Scenario:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#analysis">Analysis:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#contextual-analysis">Contextual Analysis:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#confirmation-of-anomaly">Confirmation of Anomaly:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#response">Response:</a></li></ol></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#environmental-context">Environmental Context</a><ol><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#scenario">Scenario:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#environmental-context-analysis">Environmental Context Analysis:</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#response-based-on-environmental-context">Response Based on Environmental Context:</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#implementation-of-contextual-analysis">Implementation of Contextual Analysis</a><ol><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#security-information-and-event-management-siem-systems">Security Information and Event Management (SIEM) Systems</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#machine-learning-and-artificial-intelligence">Machine Learning and Artificial Intelligence</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#automation-and-orchestration">Automation and Orchestration</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#incident-response-planning">Incident Response Planning</a></li></ol></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#challenges-and-considerations">Challenges and Considerations</a><ol><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#false-positives-and-negatives">False Positives and Negatives</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#data-privacy-and-compliance">Data Privacy and Compliance</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#continuous-monitoring">Continuous Monitoring</a></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#skill-and-resource-requirements">Skill and Resource Requirements</a></li></ol></li><li><a href="https://svenruppert.com/2024/02/05/contextual-analysis-in-cybersecurity/#conclusion">Conclusion</a></li></ol><h2 id="key-concepts">Key Concepts</h2><h3 id="situational-awareness">Situational Awareness</h3><p>Contextual analysis begins with establishing situational awareness—a comprehensive understanding of an organization&rsquo;s network, systems, and data. This includes mapping out assets, identifying vulnerabilities, and recognizing standard patterns of behaviour. By understanding the baseline, anomalies and potential threats can be detected more efficiently.</p><p>Situational awareness in cybersecurity can be illustrated through an example involving network activity. Imagine a typical day within an organization where employees access company resources and network traffic follows regular patterns.</p><h4 id="normal-scenario">Normal Scenario:</h4><p>During working hours, employees log in to their computers, access specific servers, and engage in routine activities such as sending emails, accessing shared files, and using approved applications. During this time, the Network logs consistently show a steady flow of data between internal devices.</p><h4 id="anomalous-scenario">Anomalous Scenario:</h4><p>One day, the network logs indicate an unusually high volume of data being transmitted between an employee&rsquo;s workstation and a server containing sensitive financial information. This activity is occurring during non-working hours when such data transfers are atypical. Simultaneously, the employee&rsquo;s account is attempting to access multiple systems that are not part of their regular job responsibilities.</p><h4 id="situational-awareness-analysis">Situational Awareness Analysis:</h4><p>Situational awareness would involve recognizing the deviation from the baseline of regular network activity. Security analysts would investigate the anomalous behaviour, considering factors such as the timing of the activity, the specific systems involved, and the employee&rsquo;s role. By correlating data from various sources like logs, endpoint security tools, and user behaviour analytics, the analysts aim to understand the broader context of the situation.</p><h4 id="potential-explanations">Potential Explanations:</h4><p>It could be a legitimate activity, such as an employee working on an urgent project during non-working hours. However, this should be validated to ensure it&rsquo;s not a compromise or misuse of credentials. Alternatively, it might indicate a compromised account, where an external actor or malicious insider is attempting to exfiltrate sensitive data.</p><h4 id="response">Response:</h4><p>Based on the situational awareness analysis, appropriate responses can be initiated, such as blocking the suspicious activity, notifying the employee to confirm the legitimacy of their actions, and escalating the incident for further investigation if needed. Automated responses, such as temporarily blocking the account, could be triggered to contain potential threats while the situation is assessed.</p><p>In this example, situational awareness involves understanding the typical patterns of network activity, identifying anomalies, and responding appropriately to potential security threats based on a broader understanding of the context surrounding the events.</p><h3 id="data-correlation">Data Correlation</h3><p>In a cybersecurity context, data is generated from various sources, such as network logs, endpoint devices, and application activity. The contextual analysis involves correlating this diverse set of data to create a more complete picture of an event or potential threat. Correlation helps distinguish between isolated incidents and coordinated attacks.</p><p>Let&rsquo;s consider an example involving a potential security incident and how data correlation helps in identifying and responding to the threat:</p><h4 id="scenario">Scenario:</h4><p>An organization has an SIEM (Security Information and Event Management) system in place, collecting data from different sources such as firewall logs, antivirus alerts, and server logs. One day, the antivirus system generates an alert about a suspicious file being detected on a user&rsquo;s workstation.</p><h4 id="data-sources">Data Sources:</h4><p><strong>Firewall Logs</strong> : Show an unusual outbound connection from the same user&rsquo;s workstation to an external IP address.</p><p><strong>Antivirus Alert</strong> : Indicates the detection of a malware file on the user&rsquo;s workstation.</p><p><strong>Server Logs</strong> : Display an increase in failed login attempts on a critical server from the same user&rsquo;s credentials.</p><h4 id="correlation">Correlation:</h4><p>Without data correlation, each event might be treated in isolation, potentially leading to incomplete insights into the security incident. Data correlation involves combining information from these different sources to create a more detailed picture of the situation.</p><h4 id="correlated-analysis">Correlated Analysis:</h4><p><strong>Firewall Logs + Antivirus Alert</strong> : The outbound connection in the firewall logs and the antivirus alert both relate to the same user&rsquo;s workstation, suggesting that the system might be compromised.</p><p><strong>Antivirus Alert + Server Logs</strong> : The antivirus alert indicates a malware file on the user&rsquo;s workstation, and the server logs show an increase in failed login attempts using the same credentials. This correlation suggests that the malware might be attempting to propagate to other systems using stolen credentials.</p><p><strong>Firewall Logs + Server Logs</strong> : Combining information from firewall logs and server logs reveals that the suspicious outbound connection coincides with the increase in failed login attempts on the critical server. This correlation strengthens the hypothesis that the compromised workstation is attempting unauthorized access to the server.</p><h4 id="response-1">Response:</h4><p>With a correlated analysis, security analysts can respond more effectively. They might isolate the compromised workstation from the network, initiate a malware scan, reset the affected user&rsquo;s credentials, and investigate the attempted unauthorized access to the critical server.</p><p>In this example, data correlation helps connect the dots between seemingly isolated security events, enabling a more accurate understanding of the incident. It allows security teams to respond promptly and comprehensively to potential threats by considering the broader context of the data.</p><h3 id="threat-intelligence-integration">Threat Intelligence Integration</h3><p>Contextual analysis benefits from the integration of threat intelligence. By leveraging information about known threats, vulnerabilities, and attack techniques, organizations can contextualize their security data. This integration enhances the ability to identify and respond to emerging threats that align with known patterns.</p><p>Here&rsquo;s an example illustrating how threat intelligence integration can be applied:</p><h4 id="scenario-1">Scenario:</h4><p>An organization operates in the financial sector and has a robust cybersecurity infrastructure, including an SIEM system and an intrusion detection system (IDS). The organization subscribes to a threat intelligence feed that provides real-time information about emerging cyber threats, including indicators of compromise (IoCs), malware signatures, and tactics, techniques, and procedures (TTPs) associated with specific threat actors.</p><h4 id="threat-intelligence-integration-1">Threat Intelligence Integration:</h4><p><strong>New Threat Indicator Detected</strong> : The threat intelligence feed identifies a new malicious IP address associated with a known cybercriminal group targeting financial institutions.</p><p><strong>Integration with SIEM</strong> : The threat intelligence feed is integrated into the organization&rsquo;s SIEM system. This integration allows the SIEM to update its rules and correlation logic automatically based on the newly identified threat indicator.</p><p><strong>SIEM Alerts Triggered</strong> : As the SIEM continuously monitors network and system activities, it detects network traffic attempting to communicate with the identified malicious IP address. This triggers alerts within the SIEM system.</p><p><strong>Correlation with IDS Alerts</strong> : Simultaneously, the IDS generates alerts about suspicious activities that align with the TTPs associated with the cybercriminal group mentioned in the threat intelligence feed.</p><p><strong>Analysis and Confirmation</strong> : Security analysts, equipped with the integrated threat intelligence, can quickly correlate the SIEM and IDS alerts. They recognize that the detected activities match the known patterns of the cybercriminal group targeting financial institutions.</p><h4 id="response-2">Response:</h4><p>With the contextual information from the threat intelligence feed, the security team can tailor their response more effectively. They may block the malicious IP address at the firewall, update antivirus signatures, and implement additional security controls to mitigate the specific threats associated with the identified threat actor.</p><p>In this example, threat intelligence integration allows the organization to leverage external insights to fortify its defences and respond swiftly to specific threats. This collaborative approach enhances the organization&rsquo;s cybersecurity resilience against targeted attacks.</p><h3 id="user-behavior-analysis">User Behavior Analysis</h3><p>Understanding typical user behaviour is crucial for contextual analysis. This involves recognizing patterns of activity associated with legitimate users and identifying anomalies that may indicate compromised accounts or malicious insider activities. User behaviour analytics tools play a significant role in this aspect of contextual analysis.</p><p>Here&rsquo;s an example illustrating how user behaviour analysis can be applied:</p><h4 id="scenario-2">Scenario:</h4><p>A medium-sized technology company has implemented user behaviour analysis as part of its cybersecurity strategy. The company&rsquo;s network includes various departments, each with specific access rights to sensitive data and systems. David, a software developer, typically accesses a particular set of servers and repositories related to his development tasks.</p><h4 id="user-behavior-analysis-1">User Behavior Analysis:</h4><p><strong>Baseline Establishment</strong> : The system establishes a baseline of normal behaviour for David. This includes the typical servers he accesses, the times he logs in, and the types of files he interacts with.</p><p><strong>Anomaly Detection</strong> : One day, the user behaviour analysis system flags an anomaly. David is accessing a server he has never accessed before, and he&rsquo;s doing so during non-working hours.</p><p><strong>Correlation with Other Data</strong> : The system correlates this anomaly with other data sources, such as firewall logs and VPN activity. It discovers that, around the same time, there is an unusual outbound connection from David&rsquo;s workstation to an external IP address.</p><p><strong>Contextual Analysis</strong> : Security analysts perform contextual analysis. They check the nature of the server David accessed and the destination of the outbound connection. They also cross-reference this with threat intelligence to see if the external IP address is associated with known malicious activity.</p><p><strong>Confirmation of Anomaly</strong> : Through the analysis, it is confirmed that David&rsquo;s account has been compromised. An attacker gained access to his credentials, leading to unauthorized access to a server and an attempt to communicate with a potentially malicious external entity.</p><h4 id="response-3">Response:</h4><p>The security team takes immediate action. They isolate John&rsquo;s compromised account, reset his credentials, and investigate the extent of the compromise. Simultaneously, they implement additional security measures, such as two-factor authentication, to prevent future unauthorized access.</p><p>In this example, user behaviour analysis assists in identifying a potential security incident involving a compromised user account. By continuously monitoring and analyzing user activities, organizations can enhance their ability to detect and respond to security threats in a timely manner.</p><h3 id="temporal-analysis">Temporal Analysis</h3><p>Contextual analysis considers the timing of events. Analyzing when certain activities occur can provide insights into their legitimacy. For example, a user accessing sensitive data during non-working hours might raise suspicions. Temporal analysis helps in distinguishing between normal and potentially malicious activities.</p><p>Here&rsquo;s an example illustrating how temporal analysis can be applied:</p><h4 id="scenario-3">Scenario:</h4><p>A financial institution operates a web-based application that facilitates online transactions for its customers. The system logs various events, including user logins, financial transactions, and database access.</p><h4 id="analysis">Analysis:</h4><p><strong>Baseline Establishment</strong> : The security team establishes a baseline of standard temporal patterns for user logins and financial transactions. For instance, during weekdays, there is a peak in user activity during business hours, and financial transactions are more frequent during specific time windows.</p><p><strong>Anomaly Detection</strong> : Temporal analysis flags an anomaly: A sudden surge in financial transactions is observed during non-business hours on a weekend. Simultaneously, there&rsquo;s an unusual pattern of multiple failed login attempts to user accounts.</p><p><strong>Correlation with Other Data</strong> : The system correlates temporal anomalies with other data sources, such as network logs and intrusion detection system (IDS) alerts. It discovers that the increased financial transactions coincide with a spike in outbound traffic to an unfamiliar IP address.</p><h4 id="contextual-analysis">Contextual Analysis:</h4><p>Security analysts perform contextual analysis to understand the nature of the financial transactions and the destination of the outbound traffic. They also check if this aligns with known threat indicators or attack patterns.</p><h4 id="confirmation-of-anomaly">Confirmation of Anomaly:</h4><p>Through the analysis, it is confirmed that the temporal anomalies indicate a potential security incident. An attacker has gained unauthorized access to user accounts, attempting fraudulent financial transactions during a time when security defences might be less vigilant.</p><h4 id="response-4">Response:</h4><p>The security team takes immediate action to contain the incident. They block unauthorized transactions, reset compromised user accounts, and implement additional controls to prevent further unauthorized access.</p><p>In this example, temporal analysis plays a crucial role in detecting unusual patterns of financial transactions and login attempts, allowing the organization to respond promptly to a potential security threat.</p><h3 id="environmental-context">Environmental Context</h3><p>Considering the broader environmental context is essential. This includes factors like the industry in which an organization operates, geopolitical events, and regulatory requirements. Understanding the external context helps in prioritizing threats and aligning cybersecurity efforts with the specific risks an organization faces.</p><p>Here&rsquo;s an example illustrating how environmental context can impact cybersecurity:</p><h4 id="scenario-4">Scenario:</h4><p>A multinational healthcare organization stores sensitive patient information on its servers and operates in a highly regulated industry.</p><h4 id="environmental-context-analysis">Environmental Context Analysis:</h4><p><strong>Industry Regulations</strong> : The healthcare industry is subject to strict data protection regulations, such as the Health Insurance Portability and Accountability Act (HIPAA) in the United States. The organization recognizes that any security incidents leading to data breaches could result in severe legal and financial consequences.</p><p><strong>Geopolitical Events</strong> : There is a surge in cyber-espionage activities targeting healthcare organizations due to geopolitical tensions. Recent news reports highlight increased hacking attempts and data theft in the industry.</p><p><strong>Emerging Cyber Threats</strong> : The organization monitors threat intelligence feeds indicating a rise in ransomware attacks targeting healthcare institutions. Recent incidents in the industry have resulted in data encryption and ransom demands.</p><p><strong>Internal Changes</strong> : The organization is undergoing a digital transformation, implementing new technologies to enhance patient care. This introduces additional attack surfaces and potential vulnerabilities that need to be addressed.</p><h4 id="response-based-on-environmental-context-"><strong>Response Based on Environmental Context</strong> :</h4><p>Recognizing the environmental context, the organization takes several strategic cybersecurity measures:</p><p><strong>Enhanced Regulatory Compliance</strong> : The cybersecurity strategy places a strong emphasis on compliance with healthcare regulations. The organization invests in solutions that specifically address HIPAA requirements, such as encryption and access controls.</p><p><strong>Geopolitical Threat Mitigation</strong> : The security team increases monitoring and implements additional controls to defend against cyber-espionage activities targeting healthcare organizations. They collaborate with industry peers to share threat intelligence and best practices.</p><p><strong>Ransomware Preparedness</strong> : Given the rising threat of ransomware, the organization conducts regular training for employees on recognizing phishing attempts and establishes robust backup and recovery processes to minimize the impact of a potential ransomware attack.</p><p><strong>Adaptive Security Measures</strong> : The cybersecurity strategy acknowledges the evolving nature of the organization&rsquo;s digital landscape. Security controls are regularly updated to accommodate new technologies introduced during the digital transformation, ensuring a proactive approach to emerging threats.</p><p>In this example, environmental context plays a pivotal role in shaping the organization&rsquo;s cybersecurity strategy, allowing it to effectively navigate industry-specific challenges and emerging threats.</p><h2 id="implementation-of-contextual-analysis">Implementation of Contextual Analysis</h2><h3 id="security-information-and-event-management-siem-systems">Security Information and Event Management (SIEM) Systems</h3><p>SIEM systems play a central role in contextual analysis by aggregating and analyzing security data from various sources. These platforms use correlation rules and behavioural analytics to identify potential threats. The integration of threat intelligence feeds further enhances their ability to provide context to security events.</p><p>It is a comprehensive solution designed to provide a holistic view of an organization&rsquo;s information security. It combines Security Information Management (SIM) and Security Event Management (SEM) into one integrated platform.</p><p>The primary goal of an SIEM system is to provide real-time analysis of security alerts generated throughout an organization&rsquo;s IT infrastructure.</p><p>Here are the key components and functionalities of an SIEM system:</p><p><strong>Data Collection</strong> : SIEM systems collect and aggregate log data generated throughout the organization&rsquo;s technology infrastructure, from host systems and applications to network and security devices.</p><p><strong>Normalization and Correlation</strong> : The collected data is normalized to a standard format, allowing for more straightforward analysis. SIEM tools also correlate related events to help identify patterns and potential security threats.</p><p><strong>Alerting and Notification</strong> : SIEM systems can generate alerts in real-time or near real-time, notifying security administrators of potential security incidents or policy violations. Alerts are often prioritized based on severity.</p><p><strong>Dashboards and Reporting</strong> : SIEM solutions provide dashboards and reports that offer insights into the security status of an organization. These visualizations help security professionals understand the overall security posture, trends, and potential areas of concern.</p><p><strong>Incident Response</strong> : SIEM systems assist in incident response by providing detailed information about security incidents, helping security teams investigate and mitigate threats more effectively.</p><p><strong>Compliance Monitoring</strong> : Many organizations use SIEM tools to meet regulatory compliance requirements by monitoring and reporting on activities that are subject to specific regulations.</p><p><strong>User and Entity Behavior Analytics (UEBA)</strong> : Some SIEM solutions incorporate UEBA, which uses machine learning and statistical analysis to detect abnormal behaviour patterns among users and entities, helping to identify potential insider threats.</p><p><strong>Integration with Other Security Tools</strong> : SIEM systems often integrate with other security solutions, such as intrusion detection/prevention systems, antivirus software, and vulnerability management tools, to provide a more comprehensive security posture.</p><p><strong>Log Retention and Storage</strong> : SIEM tools typically store log and event data for an extended period, allowing for historical analysis and compliance reporting.</p><p>By centralizing and analyzing security event data from across an organization&rsquo;s technology infrastructure, SIEM systems play a crucial role in enhancing an organization&rsquo;s ability to detect and respond to security incidents effectively. They are an essential component of a robust cybersecurity strategy.</p><h3 id="machine-learning-and-artificial-intelligence">Machine Learning and Artificial Intelligence</h3><p>Machine learning algorithms contribute significantly to contextual analysis. These technologies can analyze vast amounts of data, recognize patterns, and identify anomalies that may elude traditional rule-based systems. AI-driven tools excel in adapting to evolving threats by continuously learning from new data.</p><h3 id="automation-and-orchestration">Automation and Orchestration</h3><p>Automated responses to security incidents are a natural extension of contextual analysis. When an anomalous event is detected, automatic responses can be triggered to contain or mitigate the threat. Orchestration ensures that these automated actions are coordinated across different security tools and systems.</p><h3 id="incident-response-planning">Incident Response Planning</h3><p>Contextual analysis is an integral part of incident response planning. Organizations should have predefined workflows that consider the context of an incident. This includes understanding the potential impact, the criticality of affected systems, and the broader implications for the business.</p><h2 id="challenges-and-considerations">Challenges and Considerations</h2><h3 id="false-positives-and-negatives">False Positives and Negatives</h3><p>One of the challenges in contextual analysis is the balance between minimizing false positives (incorrectly identifying benign events as threats) and avoiding false negatives (failing to detect actual threats). Achieving this balance requires fine-tuning correlation rules and leveraging advanced analytics.</p><h3 id="data-privacy-and-compliance">Data Privacy and Compliance</h3><p>As contextual analysis involves analyzing diverse data sources, ensuring compliance with data privacy regulations is crucial. Organizations must strike a balance between practical analysis and respecting the privacy rights of individuals.</p><h3 id="continuous-monitoring">Continuous Monitoring</h3><p>Contextual analysis is an ongoing process that requires continuous monitoring and adaptation. Cybersecurity threats evolve, and so should contextual analysis strategies. Regularly updating correlation rules, threat intelligence feeds and adjusting algorithms is essential to stay ahead of emerging threats.</p><h3 id="skill-and-resource-requirements">Skill and Resource Requirements</h3><p>Implementing practical contextual analysis requires skilled cybersecurity professionals and adequate resources. Organizations need personnel who can interpret the context of security events and make informed decisions. Additionally, investing in advanced technologies and training programs is essential.</p><h2 id="conclusion">Conclusion</h2><p>Contextual analysis is a dynamic and integral part of modern cybersecurity strategies. By understanding the broader context of security events, organizations can enhance their ability to detect, respond to, and mitigate cyber threats effectively. As cyber threats continue to evolve, contextual analysis will play a pivotal role in securing digital assets and maintaining the resilience of organizations in the face of cyber challenges.</p>
]]></content:encoded><category>Security</category><media:content url="https://svenruppert.com/images/2023/12/IMG_6469.JPG_compressed.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2023/12/IMG_6469.JPG_compressed.jpeg"/><enclosure url="https://svenruppert.com/images/2023/12/IMG_6469.JPG_compressed.jpeg" type="image/jpeg" length="0"/></item><item><title>What is a Common Weakness Enumeration - CWE</title><link>https://svenruppert.com/posts/what-is-a-common-weakness-enumeration-cwe/</link><pubDate>Wed, 10 Jan 2024 17:24:15 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/what-is-a-common-weakness-enumeration-cwe/</guid><description>CWE stands for Common Weakness Enumeration. It is a community-developed list of software and hardware weakness types that can serve as a common language for describing, sharing, and identifying security vulnerabilities in software systems. CWE aims to provide a standardized way of identifying and categorizing vulnerabilities, making it easier for software developers, testers, and security professionals to discuss and address security issues.</description><content:encoded>&lt;![CDATA[<p>CWE stands for Common Weakness Enumeration. It is a community-developed list of software and hardware weakness types that can serve as a common language for describing, sharing, and identifying security vulnerabilities in software systems. CWE aims to provide a standardized way of identifying and categorizing vulnerabilities, making it easier for software developers, testers, and security professionals to discuss and address security issues.</p><ol><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#why-do-we-need-a-common-weakness-enumeration">Why Do We Need A Common Weakness Enumeration?</a><ol><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#standardized-language">Standardized Language:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#improved-awareness">Improved Awareness:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#efficient-communication">Efficient Communication:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#systematic-analysis">Systematic Analysis:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#security-education-and-training">Security Education and Training:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#tool-integration">Tool Integration:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#vulnerability-management">Vulnerability Management:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#community-collaboration">Community Collaboration:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#regulatory-compliance">Regulatory Compliance:</a></li></ol></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#details-about-common-weakness-enumeration">Details about Common Weakness Enumeration</a><ol><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#development-and-maintenance">Development and Maintenance:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#standardized-identifiers">Standardized Identifiers:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#categories-and-views">Categories and Views:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#relationships-and-mappings">Relationships and Mappings:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#community-involvement">Community Involvement:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#software-assurance">Software Assurance:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#integration-with-other-standards">Integration with other Standards:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#education-and-training">Education and Training:</a></li></ol></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#the-main-components-of-the-cwe-structure">The main components of the CWE structure:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#an-example-for-the-java-world">An Example for the Java-World</a><ol><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#cwe-129-improper-validation-of-array-index">CWE-129: Improper Validation of Array Index</a><ol><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#description">Description:</a></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#example-in-java">Example in Java:</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2024/01/10/what-is-a-common-weakness-enumeration-cwe/#a-list-of-example-cwe-s">A List Of Example CWE´s</a></li></ol><h2 id="why-do-we-need-a-common-weakness-enumeration">Why Do We Need A Common Weakness Enumeration?</h2><p>Common Weakness Enumeration (CWE) serves several essential purposes in cybersecurity and software development. Here are some key reasons why CWE is needed:</p><h3 id="standardized-language">Standardized Language:</h3><p>CWE provides a standardized and common language for describing and categorizing software and hardware weaknesses. This common terminology helps improve communication among developers, testers, and security professionals, fostering a shared understanding of vulnerabilities.</p><h3 id="improved-awareness">Improved Awareness:</h3><p>CWE raises awareness about common weaknesses and vulnerabilities in software systems. This awareness is essential for developers and security practitioners to proactively identify and address potential issues during the software development life cycle.</p><h3 id="efficient-communication">Efficient Communication:</h3><p>Using unique identifiers (CWE IDs) for each weakness allows for efficient communication and reference. When discussing vulnerabilities, using CWE IDs ensures clarity about which specific weakness is being referred to.</p><h3 id="systematic-analysis">Systematic Analysis:</h3><p>CWE organizes weaknesses into categories and views, enabling a systematic analysis of vulnerabilities. This organization helps identify patterns and trends in security issues, allowing for more effective risk assessment and mitigation strategies.</p><h3 id="security-education-and-training">Security Education and Training:</h3><p>CWE is used as a foundation for security education and training programs. Developers can enhance their skills and knowledge by understanding common weaknesses, bettering them to write secure code and design robust systems.</p><h3 id="tool-integration">Tool Integration:</h3><p>Many security tools and systems use CWE to categorize and report vulnerabilities. This integration streamlines identifying and remedying weaknesses by providing a standardized way for different tools to communicate and share information about security issues.</p><h3 id="vulnerability-management">Vulnerability Management:</h3><p>CWE contributes to effective vulnerability management by providing a comprehensive list of weaknesses. This allows organizations to prioritize and address vulnerabilities based on their severity and potential impact on the system.</p><h3 id="community-collaboration">Community Collaboration:</h3><p>The collaborative development and maintenance of CWE involve input from a wide range of stakeholders, including researchers, industry professionals, and organizations. This collaborative approach ensures that CWE remains comprehensive, relevant, and reflective of the evolving landscape of software vulnerabilities.</p><h3 id="regulatory-compliance">Regulatory Compliance:</h3><p>Some regulatory frameworks and standards reference or require CWE to identify and manage software weaknesses. Compliance with these standards may be necessary for organizations operating in specific industries or regions.</p><p>In summary, Common Weakness Enumeration is crucial in enhancing cybersecurity practices by providing a standardized and widely accepted framework for identifying, categorizing, and addressing software weaknesses and vulnerabilities. It contributes to a more secure software development and deployment ecosystem by promoting awareness, communication, and collaboration within the cybersecurity community.</p><h2 id="details-about-common-weakness-enumeration">Details about Common Weakness Enumeration</h2><h3 id="development-and-maintenance">Development and Maintenance:</h3><p>CWE is developed and maintained by the MITRE Corporation, a not-for-profit organization that operates Federally Funded Research and Development Centers (FFRDCs) in the United States.</p><h3 id="standardized-identifiers">Standardized Identifiers:</h3><p>Each weakness in the CWE list is assigned a unique CWE ID identifier. This helps in unambiguously referring to specific weaknesses when discussing vulnerabilities.</p><h3 id="categories-and-views">Categories and Views:</h3><p>CWE organizes weaknesses into categories and views. Categories group similar weaknesses together, making understanding and addressing issues systematically easier. Views provide different perspectives on the data, allowing users to focus on specific aspects of weaknesses.</p><h3 id="relationships-and-mappings">Relationships and Mappings:</h3><p>CWE captures relationships between weaknesses, showing how they might be related or lead to other vulnerabilities. It also provides mappings to other relevant standards and frameworks, facilitating integration with different tools and processes.</p><h3 id="community-involvement">Community Involvement:</h3><p>The development of CWE involves collaboration with the broader cybersecurity community, including researchers, developers, and security professionals. Input is collected to ensure that the list remains comprehensive and up-to-date.</p><h3 id="software-assurance">Software Assurance:</h3><p>CWE is part of a broader initiative focused on improving software assurance. Providing a common language for describing weaknesses contributes to building more secure and robust software systems.</p><h3 id="integration-with-other-standards">Integration with other Standards:</h3><p>CWE is often used alongside standards like the Common Vulnerability Scoring System (CVSS) and the Common Vulnerability and Exposure (CVE) system. Together, These standards help assess, score, and address vulnerabilities in a coordinated manner.</p><h3 id="education-and-training">Education and Training:</h3><p>CWE is used for educational purposes, helping individuals and organizations better understand common software weaknesses and vulnerabilities. Training materials and resources are developed to support this educational aspect.</p><p>In summary, Common Weakness Enumeration plays a crucial role in standardizing the identification and classification of software weaknesses, providing a foundation for improving cybersecurity practices across the software development and security communities.</p><h2 id="the-main-components-of-the-cwe-structure">The main components of the CWE structure:</h2><p><strong>Entry Point</strong> :</p><p>You have the entry points at the top level of the hierarchy. These represent high-level categories or groups of weaknesses. Examples of entry points include &ldquo;CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer&rdquo; and &ldquo;CWE-200: Exposure of Sensitive Information to an Unauthorized Actor.&rdquo;</p><p><strong>Categories</strong> :</p><p>Entry points are further divided into categories. Categories group related weaknesses together based on similar characteristics or types of vulnerabilities. For example, under &ldquo;CWE-119,&rdquo; you might have categories like &ldquo;CWE-120: Buffer Copy without Checking Size of Input (&lsquo;Classic Buffer Overflow&rsquo;)&rdquo; or &ldquo;CWE-121: Stack-based Buffer Overflow.&rdquo;</p><p><strong>Weaknesses</strong> :</p><p>Categories are broken down into specific weaknesses. Each weakness has a unique (CWE ID) identifier and detailed description. For example, under &ldquo;CWE-120,&rdquo; you might have particular weaknesses like &ldquo;CWE-120: Classic Buffer Overflow in strcat()&rdquo; or &ldquo;CWE-121: Stack-based Buffer Overflow in strcpy()&rdquo;.</p><p><strong>Relationships</strong> :</p><p>CWE also captures relationships between weaknesses. This includes hierarchical relationships, where a more general weakness encompasses more specific weaknesses. Additionally, it identifies other types of relationships, such as dependencies, mappings to other standards, and variations.</p><p><strong>Views</strong> :</p><p>CWE provides different views to offer alternative perspectives on the data. Views may focus on specific aspects of weaknesses, and users can switch between views to examine the information from different angles.</p><p><strong>Mitigations</strong> :</p><p>Each weakness entry includes information on potential mitigations or ways to address and prevent the weakness. This helps users understand how to secure their software against specific vulnerabilities.</p><h2 id="an-example-for-the-java-world">An Example for the Java-World</h2><p>One common weakness is the improper validation of array indices, which can lead to various security issues, including buffer overflows. CWE-129 specifically addresses this issue.</p><h3 id="cwe-129-improper-validation-of-array-index">CWE-129: Improper Validation of Array Index</h3><h4 id="description">Description:</h4><p>Improper validation of array indices in Java can lead to out-of-bounds read or write access, potentially causing unexpected behaviour, crashes, or security vulnerabilities.</p><h4 id="example-in-java">Example in Java:</h4><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ArrayIndexExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="o">[]</span><span class="w"/><span class="n">numbers</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">{</span><span class="n">1</span><span class="p">,</span><span class="w"/><span class="n">2</span><span class="p">,</span><span class="w"/><span class="n">3</span><span class="p">,</span><span class="w"/><span class="n">4</span><span class="p">,</span><span class="w"/><span class="n">5</span><span class="p">};</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Incorrect: Accessing an array element without proper bounds checking</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">index</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">10</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">numbers</span><span class="o">[</span><span class="n">index</span><span class="o">]</span><span class="p">;</span><span class="w"/><span class="c1">// This can lead to ArrayIndexOutOfBoundsException</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Correct: Adding proper bounds checking to prevent array index issues</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">index</span><span class="w"/><span class="o">&gt;=</span><span class="w"/><span class="n">0</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">index</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">numbers</span><span class="p">.</span><span class="na">length</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">numbers</span><span class="o">[</span><span class="n">index</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Value at index "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">index</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">": "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">value</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Invalid index: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">index</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the improper validation of the array index occurs when trying to access an element at the index<code>**10**</code> without checking whether it is within the array&rsquo;s bounds. This can result in an<code>**ArrayIndexOutOfBoundsException**</code> at runtime. The corrected version includes proper bounds checking to ensure the index is within the valid range before accessing the array.</p><p>Addressing CWEs like this one is crucial for writing secure Java code and avoiding common vulnerabilities related to array index manipulation. Keep in mind that this is just one example, and there are many other CWEs that developers should be aware of and mitigate in their Java applications.</p><h2 id="a-list-of-example-cwes">A List Of Example CWE´s</h2><p>Certainly! Below is a list of Common Weakness Enumeration (CWE) entries that are commonly associated with security issues in Java applications. Each entry includes a brief description:</p><p><strong>CWE-20: Improper Input Validation</strong></p><p>Failure to properly validate user-supplied input can lead to various security vulnerabilities, including injection attacks (e.g., SQL injection, command injection).</p><p><strong>CWE-79: Improper Neutralization of Input During Web Page Generation (&lsquo;Cross-site Scripting&rsquo;)</strong></p><p>Failure to properly sanitize user input before rendering it on a web page can lead to Cross-Site Scripting (XSS) vulnerabilities.</p><p><strong>CWE-89: Improper Neutralization of Special Elements used in an SQL Command (&lsquo;SQL Injection&rsquo;)</strong></p><p>Failure to properly validate and sanitize input used in SQL queries can result in SQL injection vulnerabilities.</p><p><strong>CWE-77: Improper Neutralization of Special Elements used in a Command (&lsquo;Command Injection&rsquo;)</strong></p><p>Like SQL injection, improper user input validation in command-based operations can lead to command injection vulnerabilities.</p><p><strong>CWE-352: Cross-Site Request Forgery (CSRF)</strong></p><p>Inadequate validation of requests can expose applications to Cross-Site Request Forgery attacks, where unauthorized actions are performed on behalf of a user.</p><p><strong>CWE-306: Missing Authentication for Critical Function</strong></p><p>Failing to enforce proper authentication for critical functions can lead to unauthorized access and potential security breaches.</p><p><strong>CWE-285: Improper Authorization</strong></p><p>Inadequate enforcement of access controls and permissions can result in unauthorized access to sensitive data or functionality.</p><p><strong>CWE-311: Missing Encryption of Sensitive Data</strong></p><p>Failure to encrypt sensitive data during storage or transmission can lead to data exposure and privacy issues.</p><p><strong>CWE-732: Incorrect Permission Assignment for Critical Resource</strong></p><p>Assigning incorrect permissions to critical resources can result in unauthorized access and potential security vulnerabilities.</p><p><strong>CWE-400: Uncontrolled Resource Consumption (&lsquo;Resource Exhaustion&rsquo;)</strong></p><p>Lack of proper resource management can lead to resource exhaustion attacks, where an attacker consumes system resources to disrupt service availability.</p><p><strong>CWE-601: URL Redirection to Untrusted Site (&lsquo;Open Redirect&rsquo;)</strong></p><p>Improper validation of user-supplied input in URL redirection can lead to open redirect vulnerabilities, potentially facilitating phishing attacks.</p><p><strong>CWE-502: Deserialization of Untrusted Data</strong></p><p>Insecure deserialization of untrusted data can lead to remote code execution and other security vulnerabilities.</p><p>It&rsquo;s important to note that the information provided here is a brief overview, and developers should refer to the official CWE website or other authoritative sources for more in-depth information and guidance on mitigating these vulnerabilities in Java applications.</p>
]]></content:encoded><category>Secure Coding Practices</category><category>Security</category><media:content url="https://svenruppert.com/images/2024/01/DSC1092_landscape-crop.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2024/01/DSC1092_landscape-crop.jpeg"/><enclosure url="https://svenruppert.com/images/2024/01/DSC1092_landscape-crop.jpeg" type="image/jpeg" length="0"/></item><item><title>Secure Coding Practices - Input Validation</title><link>https://svenruppert.com/posts/secure-coding-practices-input-validation/</link><pubDate>Wed, 13 Dec 2023 07:52:24 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/secure-coding-practices-input-validation/</guid><description>What is - Input Validation? Input validation is a process used to ensure that the data provided to a system or application meets specific criteria or constraints before it is accepted and processed. The primary goal of input validation is to improve the reliability and security of a system by preventing invalid or malicious data from causing errors or compromising the system&rsquo;s integrity.</description><content:encoded>&lt;![CDATA[<h2 id="what-is---input-validation">What is - Input Validation?</h2><p>Input validation is a process used to ensure that the data provided to a system or application meets specific criteria or constraints before it is accepted and processed. The primary goal of input validation is to improve the reliability and security of a system by preventing invalid or malicious data from causing errors or compromising the system&rsquo;s integrity.</p><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#what-is-input-validation">What is - Input Validation?</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#what-are-the-critical-aspects-of-input-validation">What are the critical aspects of input validation:</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#data-integrity">Data Integrity:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#security">Security:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#user-experience">User Experience:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#compliance">Compliance:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#preventing-bugs">Preventing Bugs:</a></li></ol></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#what-does-input-validation-have-to-do-with-security">What does input validation have to do with security?</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#preventing-injection-attacks">Preventing Injection Attacks:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#avoiding-buffer-overflows">Avoiding Buffer Overflows:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#mitigating-cross-site-request-forgery-csrf-attacks">Mitigating Cross-Site Request Forgery (CSRF) Attacks:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#protecting-against-data-tampering">Protecting Against Data Tampering:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#enhancing-authentication-and-authorisation">Enhancing Authentication and Authorisation:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#securing-file-operations">Securing File Operations:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#preventing-denial-of-service-dos-attacks">Preventing Denial-of-Service (DoS) Attacks:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#ensuring-data-integrity">Ensuring Data Integrity:</a></li></ol></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#where-can-input-validation-take-place-in-a-program">Where can input validation take place in a program?</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#user-interface-ui-level">User Interface (UI) Level:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#server-side-level">Server-Side Level:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#database-level">Database Level:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#api-level">API Level:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#middleware-level">Middleware Level:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#configuration-files">Configuration Files:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#logging-level">Logging Level:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#web-application-firewall-waf">Web Application Firewall (WAF):</a></li></ol></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#what-are-the-advantages-and-disadvantages-of-server-side-and-client-side-input-validation">What are the advantages and disadvantages of server-side and client-side input validation?</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#server-side-input-validation">Server-Side Input Validation</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#advantages">Advantages:</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#security-1">Security:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#data-integrity-1">Data Integrity:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#business-logic">Business Logic:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#user-experience-1">User Experience:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#cross-browser-compatibility">Cross-Browser Compatibility:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#code-reusability">Code Reusability:</a></li></ol></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#disadvantages">Disadvantages:</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#response-time">Response Time:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#user-experience-1">User Experience:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#bandwidth-usage">Bandwidth Usage:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#server-load">Server Load:</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#client-side-input-validation">Client-Side Input Validation</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#advantages">Advantages:</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#immediate-feedback">Immediate Feedback:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#bandwidth-efficiency">Bandwidth Efficiency:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#enhanced-user-experience">Enhanced User Experience:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#reduced-server-load">Reduced Server Load:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#reduced-round-trip-time">Reduced Round-Trip Time:</a></li></ol></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#disadvantages">Disadvantages:</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#security-risks">Security Risks:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#browser-dependency">Browser Dependency:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#code-duplication">Code Duplication:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#accessibility">Accessibility:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#browser-performance">Browser Performance:</a></li></ol></li></ol></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#lessons-learned-client-versus-server-side-validation">Lessons Learned - client- versus server-side validation:</a></li></ol></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#what-are-standard-techniques-for-input-validation">What are standard techniques for input validation?</a><ol><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#data-type-checks">Data Type Checks:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#length-checks">Length Checks:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#format-checks">Format Checks:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#range-checks">Range Checks:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#pattern-matching">Pattern Matching:</a></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#allowlisting-blocklisting">Allowlisting/Blocklisting:</a></li></ol></li><li><a href="https://svenruppert.com/2023/12/13/secure-coding-practices-input-validation/#conclusion">Conclusion:</a></li></ol><h2 id="what-are-the-critical-aspects-of-input-validation">What are the critical aspects of input validation:</h2><h3 id="data-integrity">Data Integrity:</h3><p>Input validation helps maintain data integrity by ensuring it conforms to the expected format and type. For example, if a system expects a numerical input, input validation can check whether the entered data is a number.</p><h3 id="security">Security:</h3><p>Proper input validation is crucial for preventing security vulnerabilities such as injection attacks (e.g., SQL injection, cross-site scripting) where malicious data is inserted to exploit vulnerabilities in a system.</p><h3 id="user-experience">User Experience:</h3><p>Input validation can also contribute to a better user experience by providing immediate feedback to users about the correctness of their input. Instead of allowing users to submit invalid data and encounter errors, input validation helps users correct mistakes upfront.</p><h3 id="compliance">Compliance:</h3><p>In some industries, regulatory requirements mandate proper input validation to protect sensitive information and ensure the appropriate functioning of systems.</p><h3 id="preventing-bugs">Preventing Bugs:</h3><p>Input validation can help prevent bugs and unexpected behaviour in software by ensuring that the data flowing through the system adheres to the expected standards.</p><h2 id="what-does-input-validation-have-to-do-with-security">What does input validation have to do with security?</h2><p>Input validation is crucial for security in software development because it helps prevent a wide range of security vulnerabilities and attacks. Here are some key reasons why input validation is essential for security:</p><h3 id="preventing-injection-attacks">Preventing Injection Attacks:</h3><p>Input validation helps protect against injection attacks, where malicious code is injected into user input. Common examples include SQL injection and cross-site scripting (XSS) attacks. By validating and sanitising input, developers can ensure that user input doesn&rsquo;t contain malicious code the application could execute.</p><h3 id="avoiding-buffer-overflows">Avoiding Buffer Overflows:</h3><p>Input validation helps prevent buffer overflows, a vulnerability where an attacker can exploit the program&rsquo;s memory by providing input that exceeds the allocated buffer size. By validating input size and content, developers can mitigate the risk of buffer overflow attacks.</p><h3 id="mitigating-cross-site-request-forgery-csrf-attacks">Mitigating Cross-Site Request Forgery (CSRF) Attacks:</h3><p>CSRF attacks involve tricking a user&rsquo;s browser into making unintended requests to a web application on which the user is authenticated. Proper input validation ensures that requests originate from the expected sources, reducing the risk of CSRF attacks.</p><h3 id="protecting-against-data-tampering">Protecting Against Data Tampering:</h3><p>Input validation helps protect against data tampering by ensuring that the data received by the application adheres to expected formats and constraints. This prevents attackers from manipulating data to exploit vulnerabilities or gain unauthorised access.</p><h3 id="enhancing-authentication-and-authorisation">Enhancing Authentication and Authorisation:</h3><p>Validating input is essential for enforcing proper authentication and authorisation checks. Incorrect or manipulated input could bypass authentication mechanisms or grant unauthorised access to sensitive areas of an application.</p><h3 id="securing-file-operations">Securing File Operations:</h3><p>Input validation is crucial when dealing with file uploads or file-related operations. Without proper validation, an attacker might upload malicious files or manipulate file names to execute arbitrary code on the server.</p><h3 id="preventing-denial-of-service-dos-attacks">Preventing Denial-of-Service (DoS) Attacks:</h3><p>Input validation can mitigate the impact of DoS attacks by rejecting or limiting input that could lead to resource exhaustion. For example, limiting the length of input or the rate of requests can help protect against certain types of DoS attacks.</p><h3 id="ensuring-data-integrity">Ensuring Data Integrity:</h3><p>Input validation contributes to data integrity by ensuring that the data processed by an application meets expected standards. This is important for preventing unintentional errors and maintaining the consistency and reliability of the system.</p><p>In summary, input validation is a fundamental security practice that helps build robust and resilient software systems. By validating and sanitising input data, developers can significantly reduce the risk of common security vulnerabilities and enhance the overall security posture of their applications.</p><h2 id="where-can-input-validation-take-place-in-a-program">Where can input validation take place in a program?</h2><p>Input validation is a crucial aspect of writing secure and robust software. It helps ensure that the data your program receives is of the expected format and within acceptable ranges. Input validation can take place at various levels in a program, including:</p><h3 id="user-interface-ui-level">User Interface (UI) Level:</h3><p><strong>Client-Side Validation:</strong> It can be performed using JavaScript or other client-side scripting languages in web applications to provide immediate feedback to users. However, client-side validation should not be solely relied upon for security, as malicious users can bypass it.</p><h3 id="server-side-level">Server-Side Level:</h3><p><strong>Server-Side Validation:</strong> This is the most critical layer for input validation. All input received from the client should be validated on the server to ensure that it conforms to the expected format, length, and other criteria. Server-side validation is more secure than client-side validation because it cannot be easily bypassed or tampered with by users.</p><h3 id="database-level">Database Level:</h3><p>Database Input Validation: Before storing data in a database, validating the input is essential to prevent SQL injection attacks and ensure data integrity. This can involve using parameterised queries or prepared statements to prevent malicious input from affecting the database.</p><h3 id="api-level">API Level:</h3><p><strong>API Input Validation:</strong> If your application interacts with external APIs, it&rsquo;s crucial to validate input data before sending requests. This helps prevent injection attacks and ensures the API receives the expected data.</p><h3 id="middleware-level">Middleware Level:</h3><p><strong>Middleware Validation:</strong> Middleware components may process or filter incoming data in some applications before it reach the core application logic. Input validation can be performed at this level to ensure only valid data is passed to the application.</p><h3 id="configuration-files">Configuration Files:</h3><p><strong>Configuration Input Validation:</strong> If your program reads configuration files, validate the input data to ensure it adheres to the expected format and doesn&rsquo;t introduce vulnerabilities.</p><h3 id="logging-level">Logging Level:</h3><p><strong>Logging Input Validation:</strong> When logging user inputs or other sensitive information, ensure that the logged data is properly validated to prevent accidental exposure of sensitive information or injection attacks through log manipulation.</p><h3 id="web-application-firewall-waf">Web Application Firewall (WAF):</h3><p><strong>WAF Validation:</strong> WAFs can filter and validate incoming HTTP traffic. They can help protect against common web application vulnerabilities, including input-related attacks.</p><p>By implementing input validation at multiple levels, you create a defence-in-depth strategy that enhances the security of your application. Always validate input data based on your application&rsquo;s requirements and constraints to ensure proper functionality and security.</p><h2 id="what-are-the-advantages-and-disadvantages-of-server-side-and-client-side-input-validation">What are the advantages and disadvantages of server-side and client-side input validation?</h2><p>At this point, I&rsquo;ll briefly overview the advantages and disadvantages of server-side and client-side input validation, covering various aspects of security, performance, user experience, and maintenance.</p><h3 id="server-side-input-validation">Server-Side Input Validation</h3><h4 id="advantages">Advantages:</h4><h5 id="security-1">Security:</h5><p><strong>Protection Against Malicious Input</strong> : Server-side validation is crucial for security as it prevents malicious input from reaching the database or application logic. This is especially important for avoiding SQL injection, cross-site scripting (XSS), and other security vulnerabilities.</p><p><strong>Centralised Control</strong> : Centralised validation on the server allows for consistent and thorough input data validation, reducing the risk of overlooking potential security threats.</p><h5 id="data-integrity-1">Data Integrity:</h5><p><strong>Consistent Data Quality</strong> : Server-side validation ensures that the data entering the system meets the specified criteria, maintaining data integrity and consistency throughout the application.</p><p><strong>Prevention of Data Corruption</strong> : The risk of corrupted or invalid data entering the database is minimised by validating input on the server.</p><h5 id="business-logic">Business Logic:</h5><p><strong>Enforcement of Business Rules</strong> : Server-side validation is essential for enforcing business rules and logic. This ensures that data conforms not only to technical specifications but also to the application&rsquo;s specific requirements.</p><h5 id="user-experience-1">User Experience:</h5><p><strong>Consistent Validation Messages</strong> : Server-side validation provides the opportunity to generate standardised error messages, creating a more consistent and user-friendly experience for end-users.</p><h5 id="cross-browser-compatibility">Cross-Browser Compatibility:</h5><p><strong>Browser Independence</strong> : Server-side validation is not dependent on the user&rsquo;s browser, making it more reliable and consistent across different browser environments.</p><h5 id="code-reusability">Code Reusability:</h5><p><strong>Centralised Validation Logic</strong> : Server-side validation allows for the centralisation of validation logic, promoting code reusability across multiple application parts.</p><h4 id="disadvantages">Disadvantages:</h4><h5 id="response-time">Response Time:</h5><p><strong>Increased Round-Trip Time</strong> : Server-side validation requires a round-trip to the server, leading to potential delays in response time, especially in applications with high latency.</p><h5 id="user-experience-2">User Experience:</h5><p><strong>Delayed Feedback</strong> : Users may experience delays in receiving feedback on their input, leading to a less responsive and potentially frustrating user experience.</p><h5 id="bandwidth-usage">Bandwidth Usage:</h5><p><strong>Increased Bandwidth Usage</strong> : Each validation request sent to the server consumes bandwidth, which can be a concern in bandwidth-sensitive environments or for users with limited data plans.</p><h5 id="server-load">Server Load:</h5><p><strong>Increased Server Load</strong> : Server-side validation contributes to server load, and in high-traffic applications, this can lead to resource contention and decreased overall performance.</p><h3 id="client-side-input-validation">Client-Side Input Validation</h3><h4 id="advantages-1">Advantages:</h4><h5 id="immediate-feedback">Immediate Feedback:</h5><p><strong>Real-time Validation</strong> : Client-side validation provides immediate feedback to users, improving the application&rsquo;s overall responsiveness and perceived speed.</p><h5 id="bandwidth-efficiency">Bandwidth Efficiency:</h5><p><strong>Reduced Server Requests</strong> : Client-side validation minimises the number of requests to the server, conserving bandwidth and improving overall network efficiency.</p><h5 id="enhanced-user-experience">Enhanced User Experience:</h5><p><strong>Responsive Interactivity</strong> : By validating input on the client side, users receive instant feedback, creating a more interactive and responsive user experience.</p><h5 id="reduced-server-load">Reduced Server Load:</h5><p><strong>Offloading Server Processing</strong> : Client-side validation offloads some processing tasks from the server, reducing the overall load and improving server performance.</p><h5 id="reduced-round-trip-time">Reduced Round-Trip Time:</h5><p><strong>Faster Form Submissions</strong> : Since validation occurs on the client side, users experience speedier form submissions without waiting for a round-trip to the server.</p><h4 id="disadvantages-1">Disadvantages:</h4><h5 id="security-risks">Security Risks:</h5><p><strong>Vulnerability to Manipulation</strong> : Client-side validation is susceptible to manipulation by malicious users who can bypass or disable the client-side validation to submit malicious input directly to the server.</p><h5 id="browser-dependency">Browser Dependency:</h5><p><strong>Inconsistent Browser Support</strong> : Client-side validation may need consistent support across different browsers, leading to behaviour and user experience variations.</p><h5 id="code-duplication">Code Duplication:</h5><p><strong>Duplication of Validation Logic</strong> : Client-side validation requires duplicating validation logic on the server to ensure data integrity and security, leading to potential maintenance challenges.</p><h5 id="accessibility">Accessibility:</h5><p><strong>Accessibility Concerns</strong> : Relying solely on client-side validation may pose accessibility challenges for disabled users using alternative input methods or assistive technologies.</p><h5 id="browser-performance">Browser Performance:</h5><p><strong>Resource Intensive</strong> : Intensive client-side validation logic can consume significant browser resources, impacting the performance of the user&rsquo;s device.</p><h3 id="lessons-learned---client--versus-server-side-validation">Lessons Learned - client- versus server-side validation:</h3><p>In conclusion, the choice between server-side and client-side input validation depends on various factors such as security requirements, user experience goals, and application architecture. A balanced approach often involves a combination of both server-side and client-side validation to harness the advantages of each while mitigating their respective disadvantages. Careful consideration of the specific needs and constraints of the application is crucial to implementing a practical and secure input validation strategy.</p><h2 id="what-are-standard-techniques-for-input-validation">What are standard techniques for input validation?</h2><h3 id="data-type-checks">Data Type Checks:</h3><p>Ensuring that the entered data matches the expected data type (e.g., numbers, dates, strings).</p><p>Here&rsquo;s a simple Java example using the<code>instanceof</code> operator, which checks whether an object is an instance of a particular class or interface:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">DataTypeCheckExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Example with an Integer</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Object</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">42</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">value</span><span class="w"/><span class="k">instanceof</span><span class="w"/><span class="n">Integer</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">intValue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">Integer</span><span class="p">)</span><span class="w"/><span class="n">value</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The value is an Integer: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">intValue</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The value is not an Integer."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Example with a String</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Object</span><span class="w"/><span class="n">anotherValue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Hello, Java!"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">anotherValue</span><span class="w"/><span class="k">instanceof</span><span class="w"/><span class="n">String</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">stringValue</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="p">)</span><span class="w"/><span class="n">anotherValue</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The value is a String: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">stringValue</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The value is not a String."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, we have an<code>Object</code> variable (<code>value</code> and<code>anotherValue</code>) that can hold any object. We use the<code>instanceof</code> operator to check whether the object is an instance of a specific type (<code>Integer</code> or<code>String</code>). We can safely cast it to that type and perform operations accordingly if it is. If not, we handle it appropriately.</p><p>Remember, it&rsquo;s generally a good practice to use proper data types whenever possible rather than relying on type checks, but there are situations where type checks are necessary.</p><h3 id="length-checks">Length Checks:</h3><p>Verifying that the length of the input is within acceptable limits.</p><p>In Java, you can perform length checks on various data structures, such as strings or arrays, to ensure they meet specific criteria. Here&rsquo;s an example of length checks for a string in Java:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">LengthCheckExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//example string</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">myString</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Hello, World!"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Check the length of the string</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">length</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">myString</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Perform length checks</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">length</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">0</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The string is not empty."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The string is empty."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">length</span><span class="w"/><span class="o">&gt;=</span><span class="w"/><span class="n">10</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The string is at least 10 characters long."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"The string is less than 10 characters long."</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, we use the<code>length()</code> method of the<code>String</code> class to get the length of the string. We then perform two length checks: one to determine if the string is empty and another to check if it&rsquo;s at least ten characters long.</p><p>Remember that the specific length of checks you perform will depend on the requirements of your program or application. You can adapt similar checks for arrays or other data structures as needed.</p><h3 id="format-checks">Format Checks:</h3><p>Ensuring the input follows a specified format (e.g., email addresses, phone numbers).</p><p>Below is an example Java program that performs format checks for email addresses and phone numbers using regular expressions:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.regex.Matcher</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kn">import</span><span class="w"/><span class="nn">java.util.regex.Pattern</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">FormatChecker</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Example usage</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">email</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"user@example.com"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">phoneNumber</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"+1-555-555-5555"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isValidEmail</span><span class="p">(</span><span class="n">email</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Email is valid: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">email</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Email is not valid: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">email</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isValidPhoneNumber</span><span class="p">(</span><span class="n">phoneNumber</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Phone number is valid: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">phoneNumber</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Phone number is not valid: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">phoneNumber</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Check if the email address is valid</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isValidEmail</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">email</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">emailRegex</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"^[a-zA-Z0-9_+&amp;*-]+(?:\\.[a-zA-Z0-9_+&amp;*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Pattern</span><span class="w"/><span class="n">pattern</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Pattern</span><span class="p">.</span><span class="na">compile</span><span class="p">(</span><span class="n">emailRegex</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Matcher</span><span class="w"/><span class="n">matcher</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">pattern</span><span class="p">.</span><span class="na">matcher</span><span class="p">(</span><span class="n">email</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">matcher</span><span class="p">.</span><span class="na">matches</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Check if the phone number is valid</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isValidPhoneNumber</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">phoneNumber</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Accepts numbers in the format: +1-555-555-5555</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">phoneRegex</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"^\\+(?:[0-9] ?){6,14}[0-9]$"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Pattern</span><span class="w"/><span class="n">pattern</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Pattern</span><span class="p">.</span><span class="na">compile</span><span class="p">(</span><span class="n">phoneRegex</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Matcher</span><span class="w"/><span class="n">matcher</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">pattern</span><span class="p">.</span><span class="na">matcher</span><span class="p">(</span><span class="n">phoneNumber</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">matcher</span><span class="p">.</span><span class="na">matches</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example,<code>isValidEmail</code> and<code>isValidPhoneNumber</code> are two methods that use regular expressions to check if the provided email address and phone number are in the expected format. The regular expressions in this example may need adjustment based on your specific format requirements. Regular expressions are powerful tools for pattern matching but can be complex. It&rsquo;s essential to thoroughly test them with various inputs to ensure they meet your needs.</p><p>If you prefer<strong>not</strong> to use regular expressions, you can perform format checks for email addresses and phone numbers using other string manipulation techniques. Here&rsquo;s an example:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">FormatChecker</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Example usage</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">email</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"user@example.com"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">phoneNumber</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"+1-555-555-5555"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isValidEmail</span><span class="p">(</span><span class="n">email</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Email is valid: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">email</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Email is not valid: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">email</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">isValidPhoneNumber</span><span class="p">(</span><span class="n">phoneNumber</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Phone number is valid: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">phoneNumber</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Phone number is not valid: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">phoneNumber</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Check if the email address is valid</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isValidEmail</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">email</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">atIndex</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">email</span><span class="p">.</span><span class="na">indexOf</span><span class="p">(</span><span class="sc">'@'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">dotIndex</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">email</span><span class="p">.</span><span class="na">lastIndexOf</span><span class="p">(</span><span class="sc">'.'</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Ensure there is exactly one @ symbol, and it is not the first or last character</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">atIndex</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">0</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">atIndex</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">email</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"/><span class="o">-</span><span class="w"/><span class="n">1</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Ensure there is at least one dot after the @ symbol</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">dotIndex</span><span class="w"/><span class="o">&gt;</span><span class="w"/><span class="n">atIndex</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">dotIndex</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">email</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"/><span class="o">-</span><span class="w"/><span class="n">1</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Check if the phone number is valid</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isValidPhoneNumber</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">phoneNumber</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Check the length and specific characters</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">phoneNumber</span><span class="p">.</span><span class="na">length</span><span class="p">()</span><span class="w"/><span class="o">&gt;=</span><span class="w"/><span class="n">10</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">phoneNumber</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">0</span><span class="p">)</span><span class="w"/><span class="o">==</span><span class="w"/><span class="sc">'+'</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">phoneNumber</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">1</span><span class="p">)</span><span class="w"/><span class="o">&gt;=</span><span class="w"/><span class="sc">'1'</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">phoneNumber</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">1</span><span class="p">)</span><span class="w"/><span class="o">&lt;=</span><span class="w"/><span class="sc">'9'</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Check that the rest of the characters are digits or hyphens</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">for</span><span class="w"/><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">2</span><span class="p">;</span><span class="w"/><span class="n">i</span><span class="w"/><span class="o">&lt;</span><span class="w"/><span class="n">phoneNumber</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/><span class="n">i</span><span class="o">++</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">char</span><span class="w"/><span class="n">c</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">phoneNumber</span><span class="p">.</span><span class="na">charAt</span><span class="p">(</span><span class="n">i</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="p">(</span><span class="n">Character</span><span class="p">.</span><span class="na">isDigit</span><span class="p">(</span><span class="n">c</span><span class="p">)</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">c</span><span class="w"/><span class="o">==</span><span class="w"/><span class="sc">'-'</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>This example uses basic string manipulation and checks for specific conditions rather than regular expressions. Remember that regular expressions are often a more concise and powerful way to perform such checks, but this alternative approach may be suitable for more straightforward cases.</p><h3 id="range-checks">Range Checks:</h3><p>Verifying that numerical input falls within a predefined range.</p><p>You can perform range checks using conditional statements. Here&rsquo;s a simple example that demonstrates range checks for a given value within a specified range:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">RangeCheckExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Define the range</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">lowerBound</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">10</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">upperBound</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">20</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Test values</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">value1</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">15</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">value2</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">5</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">value3</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">25</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Perform range checks</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Value "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">value1</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" is in range: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">isInRange</span><span class="p">(</span><span class="n">value1</span><span class="p">,</span><span class="w"/><span class="n">lowerBound</span><span class="p">,</span><span class="w"/><span class="n">upperBound</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Value "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">value2</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" is in range: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">isInRange</span><span class="p">(</span><span class="n">value2</span><span class="p">,</span><span class="w"/><span class="n">lowerBound</span><span class="p">,</span><span class="w"/><span class="n">upperBound</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Value "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">value3</span><span class="w"/><span class="o">+</span><span class="w"/><span class="s">" is in range: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">isInRange</span><span class="p">(</span><span class="n">value3</span><span class="p">,</span><span class="w"/><span class="n">lowerBound</span><span class="p">,</span><span class="w"/><span class="n">upperBound</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Function to check if a value is within a specified range</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">isInRange</span><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">value</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">lowerBound</span><span class="p">,</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">upperBound</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">&gt;=</span><span class="w"/><span class="n">lowerBound</span><span class="w"/><span class="o">&amp;&amp;</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">&lt;=</span><span class="w"/><span class="n">upperBound</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the<code>isInRange</code> function takes a value and two bounds (lower and upper). It returns<code>true</code> if the value is within the specified range (inclusive) and<code>false</code> otherwise. The<code>main</code> method demonstrates the usage of this function for three different values and prints whether each value is in the specified range.</p><h3 id="pattern-matching">Pattern Matching:</h3><p>Checking input against a predefined pattern or regular expression.</p><p>Pattern matching was introduced in Java 16 as a preview feature and became standard in Java 17. Here&rsquo;s a simple example using pattern matching with the old<code>instanceof</code> operator:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">PatternMatchingExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Object</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Hello, Pattern Matching!"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">value</span><span class="w"/><span class="k">instanceof</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">str</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Length of the string: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">str</span><span class="p">.</span><span class="na">length</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Not a string"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, we have an<code>Object</code> called<code>value</code>, and we want to check if it is an instance of<code>String</code>. If it is, we introduce a new variable<code>str</code> of type<code>String</code> in the same<code>if</code> statement and can use it within that block. This eliminates the need for an additional cast and makes the code more concise.</p><p>In Java 16, pattern matching was introduced as a preview feature, and while<code>instanceof</code> was commonly used, there were no specific pattern-matching constructs. However, one common pattern-matching use case was in<code>switch</code> expressions. Here&rsquo;s an example:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">PatternMatchingExample</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Object</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Hello, Pattern Matching!"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">result</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">switch</span><span class="w"/><span class="p">(</span><span class="n">value</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">str</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">str</span><span class="p">.</span><span class="na">length</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">case</span><span class="w"/><span class="n">Integer</span><span class="w"/><span class="n">num</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">num</span><span class="w"/><span class="o">*</span><span class="w"/><span class="n">2</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">default</span><span class="w"/><span class="o">-&gt;</span><span class="w"/><span class="n">0</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">};</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"Result: "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">result</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>In this example, the<code>switch</code> expression performs pattern matching. Different patterns are matched depending on the type of the<code>value</code>. If it&rsquo;s a<code>String</code>, the length of the string is returned. If it&rsquo;s an<code>Integer</code>, the value is doubled. The<code>default</code> case is used if the value doesn&rsquo;t match any of the specified patterns.</p><p>Note that pattern matching with<code>instanceof</code> is more explicitly available from Java 17 onwards. But this will be a separate article.</p><h3 id="allowlistingblocklisting">Allowlisting/Blocklisting:</h3><p>Allowing or disallowing specific characters or patterns in the input.</p><p>Allowlisting and blocklisting are often used in the context of security to control access to specific resources or actions. In Java, you can implement allowlisting or blocklisting using various approaches. Below is a simple example demonstrating how you might implement a basic allowlist and blocklist for a hypothetical resource.</p><p>Let&rsquo;s say we have a class representing a resource and want to control access based on a list of allowed and disallowed entities.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">import java.util.ArrayList;</span></span><span class="line"><span class="cl">import java.util.List;</span></span><span class="line"><span class="cl">class ResourceAccessController {</span></span><span class="line"><span class="cl"> private List<span class="nt">&lt;String&gt;</span> whitelist;</span></span><span class="line"><span class="cl"> private List<span class="nt">&lt;String&gt;</span> blacklist;</span></span><span class="line"><span class="cl"> public ResourceAccessController() {</span></span><span class="line"><span class="cl"> whitelist = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> blacklist = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public void addToWhitelist(String entity) {</span></span><span class="line"><span class="cl"> whitelist.add(entity);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public void addToBlacklist(String entity) {</span></span><span class="line"><span class="cl"> blacklist.add(entity);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public boolean isAllowed(String entity) {</span></span><span class="line"><span class="cl"> // Check if the entity is in the whitelist and not in the blacklist</span></span><span class="line"><span class="cl"> return whitelist.contains(entity)<span class="err">&amp;&amp;</span> !blacklist.contains(entity);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl">public class Main {</span></span><span class="line"><span class="cl"> public static void main(String[] args) {</span></span><span class="line"><span class="cl"> ResourceAccessController accessController = new ResourceAccessController();</span></span><span class="line"><span class="cl"> // Adding entities to the whitelist</span></span><span class="line"><span class="cl"> accessController.addToWhitelist("UserA");</span></span><span class="line"><span class="cl"> accessController.addToWhitelist("UserB");</span></span><span class="line"><span class="cl"> // Adding entities to the blacklist</span></span><span class="line"><span class="cl"> accessController.addToBlacklist("UserC");</span></span><span class="line"><span class="cl"> // Checking access</span></span><span class="line"><span class="cl"> System.out.println("UserA allowed?: " + accessController.isAllowed("UserA")); // true</span></span><span class="line"><span class="cl"> System.out.println("UserB allowed?: " + accessController.isAllowed("UserB")); // true</span></span><span class="line"><span class="cl"> System.out.println("UserC allowed?: " + accessController.isAllowed("UserC")); // false</span></span><span class="line"><span class="cl"> System.out.println("UserD allowed?: " + accessController.isAllowed("UserD")); // false</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>In this example, the<code>ResourceAccessController</code> class has methods to add entities to the allowlist and blocklist. The<code>isAllowed</code> method checks if an entity is in the allowlist and not in the blocklist, thereby determining whether access is allowed. Note that this is a basic example, and in a real-world scenario, you might have more sophisticated access control mechanisms and data structures depending on your requirements.</p><h2 id="conclusion">Conclusion:</h2><p>With &ldquo;Input Validation&rdquo;, you can ward off many attack vectors on a program and thus help make your software significantly more secure. The end user benefits from a more robust and reliable solution, and the software operator can also expect fewer unpleasant surprises from hackers. All in all, it is an approach that can be implemented anywhere in the application, does not require a lot of implementation effort, and, at the same time, is a cornerstone of security.</p><p>Happy Coding</p>
]]></content:encoded><category>Java</category><category>Secure Coding Practices</category><category>Security</category><media:content url="https://svenruppert.com/images/2023/11/DSC1168_landscape-crop.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2023/11/DSC1168_landscape-crop.jpeg"/><enclosure url="https://svenruppert.com/images/2023/11/DSC1168_landscape-crop.jpeg" type="image/jpeg" length="0"/></item><item><title>Infection Method - Sub-Domain Takeover</title><link>https://svenruppert.com/posts/infection-method-sub-domain-takeover/</link><pubDate>Mon, 20 Nov 2023 14:37:29 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/infection-method-sub-domain-takeover/</guid><description>A subdomain takeover is a type of cybersecurity vulnerability that occurs when an attacker gains control of a subdomain of a website or a domain name. This attack can seriously affect the security and functionality of a web application or website. In this explanation, we&rsquo;ll look at subdomain takeovers, how they work, the risks they pose, and how to prevent them.</description><content:encoded>&lt;![CDATA[<p>A subdomain takeover is a type of cybersecurity vulnerability that occurs when an attacker gains control of a subdomain of a website or a domain name. This attack can seriously affect the security and functionality of a web application or website. In this explanation, we&rsquo;ll look at subdomain takeovers, how they work, the risks they pose, and how to prevent them.</p><h2 id="introduction-to-subdomains">Introduction to subdomains</h2><p>To understand what a subdomain takeover is, we need to understand the concept of subdomains. In the context of the Internet, a domain name such as &ldquo;example.com&rdquo; serves as the primary address for a website. However, a domain name can be further divided into subdomains, which act as separate sections or branches of the main domain. Subdomains are often used for organisational purposes, categorising different website areas, or providing specific services.</p><p>For example, consider the subdomains of the abc.com domain:</p><ul><li><a href="https://www.abc.com">www.abc.com</a> (for the main website)</li><li>blog.abc.com (for a blog section)</li><li>shop.abc.com (for an online shop)</li><li>mail.abc.com (for email services)</li><li>api.abc.com (for application programming interfaces)</li></ul><p>These subdomains can be independently configured to provide different content or applications. Although subdomains are part of the same parent domain, they may be hosted on other servers or managed by different teams within the same organisation.</p><h2 id="what-is-a-subdomain-takeover">What is a subdomain takeover?</h2><p>A subdomain takeover occurs when an unauthorised person or organisation gains control of a subdomain of a domain and thereby effectively takes ownership of it. This unauthorised control can lead to various security risks and potential misuse. The key factors contributing to subdomain takeover are misconfigurations, abandoned resources, and external service integrations.</p><p>Here are the main reasons why subdomain takeovers can occur:</p><p><a href="https://youtu.be/rhrSC_4neAY">https://youtu.be/rhrSC_4neAY</a></p><h2 id="misconfigurations">Misconfigurations:</h2><p>Web administrators and developers can accidentally misconfigure DNS records or forget to remove DNS records for no more prolonged use subdomains. These misconfigurations can allow an attacker to take control of a subdomain.</p><h2 id="abandoned-resources">Abandoned resources:</h2><p>Organisations can create subdomains for specific projects, campaigns, or temporary services and then abandon them later. If an attacker discovers an abandoned subdomain, they can claim it as their own and potentially use it for malicious purposes.</p><h2 id="external-service-integrations">External service integrations:</h2><p>Many websites integrate third-party services, such as content delivery networks (CDNs), cloud services or hosted applications. These services often require the creation of subdomains that point to the third-party service provider&rsquo;s infrastructure. If the external service becomes vulnerable, attackers can exploit it to take over the associated subdomain.</p><h2 id="how-subdomain-takeovers-work">How subdomain takeovers work</h2><p>Subdomain takeovers result from vulnerabilities due to misconfigured DNS records, abandoned resources, or compromised external services.</p><h4 id="incorrectly-configured-dns-entries">Incorrectly configured DNS entries:</h4><p><strong>CNAME Records:</strong> A common misconfiguration involves CNAME (canonical name) records. A CNAME record maps a subdomain to another domain or subdomain. If a subdomain is incorrectly pointed to a domain controlled by an attacker, the attacker can effectively take over the subdomain.</p><h4 id="wildcard-entries">Wildcard entries:</h4><p>Wildcard DNS records are used to direct all subdomains of a domain to a common destination. If a subdomain with a wildcard entry is no longer claimed or points to an attacker&rsquo;s server, the attacker can control all subdomains.</p><h4 id="abandoned-resources-1">Abandoned resources:</h4><p>Subdomains created for temporary projects, campaigns, or services may be forgotten or abandoned after their purpose has been served. Attackers can search for and claim such subdomains if they are available for registration or reconfiguration. I want to describe an example I have found several times in projects.</p><p>Let&rsquo;s assume a service is hosted externally. For example, a virtual machine is created and assigned a logical name in Heroku. This typically looks like this: You can choose a name to which the external operator&rsquo;s domain is added. In this example, it is<strong>demapp.heroku.com.</strong> This service is no longer needed and will be cancelled by the customer. However, there are still entries in the DNS configuration that point to<strong>demoapp.heroku.com</strong>. This can be, for example, a redirect from your own subdomain in this example,<strong>demoapp.abc.com</strong>. The attacker now creates an instance on Heroku and gives it the name<strong>demoapp</strong> , leading to the complete name<strong>demoapp.heroku.com</strong>. Now, the attacker only has to listen to the ports and analyse which requests arrive at this machine and can build his attack vector based on this. For example, he can set up a corresponding compromised service that responds to these requests.</p><h4 id="compromised-external-services">Compromised external services:</h4><p>Some subdomains are created to link to external services or resources provided by third parties. Attackers can tamper with the associated subdomains if these external services are compromised or misconfigured.</p><p>In these scenarios, the attacker effectively takes control of the subdomain by configuring the DNS records or exploiting vulnerabilities. Once they control the subdomain, they can host malicious content, launch phishing attacks, or commit other forms of cybercrime.</p><p>In these scenarios, the attacker effectively takes control of the subdomain by configuring the DNS records or exploiting vulnerabilities. Once they control the subdomain, they can host malicious content, launch phishing attacks, or commit other forms of cybercrime.</p><h2 id="risks-and-consequences-of-subdomain-takeovers">Risks and consequences of subdomain takeovers</h2><p>Subdomain takeovers pose significant risks to websites&rsquo; and web applications&rsquo; security, reputation and functionality. The consequences of a successful subdomain takeover can be severe and far-reaching, including:</p><h3 id="data-theft">Data theft:</h3><p>Attackers can use subdomains to steal sensitive data such as user credentials, personal information, or financial data.</p><h3 id="phishing-attacks">Phishing attacks:</h3><p>Subdomain takeovers are often used in phishing campaigns. Attackers can create convincing fake login pages or legitimate content to trick users into revealing their credentials.</p><h3 id="malware-distribution">Malware distribution:</h3><p>Malicious files or software can be hosted on the compromised subdomain, which can then be used to distribute malware to unsuspecting visitors.</p><h3 id="denylisting">Denylisting:</h3><p>Search engines and email providers can denylist the parent domain if they detect malicious activity on a subdomain. This may impact website visibility and email communications.</p><h3 id="brand-damage">Brand damage:</h3><p>A subdomain takeover can damage an organisation or website&rsquo;s reputation and lose trust among users and customers.</p><h3 id="financial-loss">Financial loss:</h3><p>Depending on the severity of the attack, a company could face financial losses, lawsuits, and fines.</p><h3 id="loss-of-control">Loss of control:</h3><p>Once an attacker has control of a subdomain, it becomes difficult for the rightful owner to regain control and limit the damage.</p><h3 id="legal-consequences">Legal consequences:</h3><p>Unauthorised access to or control over subdomains can result in legal consequences and penalties for the attacker.</p><h2 id="practical-examples">Practical examples</h2><p>To illustrate the real-world impact of subdomain takeovers, let&rsquo;s look at a few notable cases:</p><h3 id="uber">Uber:</h3><p>In 2015, researchers discovered a subdomain takeover vulnerability in Uber&rsquo;s system. The ride-hailing company had a subdomain called riders.uber.com that was vulnerable to a takeover. Attackers may have used this subdomain to launch phishing attacks against Uber users. Uber immediately addressed the issue after being informed by researchers.</p><h3 id="github-pages">GitHub pages:</h3><p>GitHub Pages is a popular platform for hosting websites and documentation. In 2017, a researcher found that GitHub Page&rsquo;s subdomains were vulnerable to a takeover when users deleted their repositories but left the associated DNS records intact. Attackers could then claim the subdomains and host malicious content. GitHub has since taken measures to prevent such takeovers.</p><h3 id="shopify">Shopify:</h3><p>Shopify, a central e-commerce platform, faced a subdomain adoption issue in 2019. An attacker discovered that he could claim abandoned Shopify store subdomains and potentially use them for fraud. Shopify has fixed the vulnerability and improved its security measures.</p><p>These cases show that subdomain takeovers can affect both small and large organisations. Timely identification and remediation of such vulnerabilities is critical to maintaining the security and trustworthiness of online services.</p><h2 id="prevent-subdomain-takeovers">Prevent subdomain takeovers</h2><p>Preventing subdomain takeovers requires a combination of proactive measures and best practices. Here are steps companies can take to mitigate the risk of subdomain takeovers:</p><h3 id="regular-dns-audits">Regular DNS Audits:</h3><p>Perform routine DNS record checks to identify misconfigurations or abandoned subdomains. Remove unnecessary DNS records and update records for active subdomains.</p><h3 id="best-practices-for-cname-records">Best practices for CNAME records:</h3><p>When using CNAME records, be careful where they point. Make sure you trust the target and check CNAME configurations regularly.</p><h3 id="wildcard-dns-records">Wildcard DNS records:</h3><p>Use wildcard DNS records carefully. Avoid directing all subdomains to a single destination, as this can create a single point of failure.</p><h3 id="subdomain-registration-guidelines">Subdomain Registration Guidelines:</h3><p>Implement clear guidelines for subdomain creation and management. Ensure that subdomains are registered with valid entities within the organisation and follow a consistent naming convention.</p><h3 id="automated-scanning">Automated scanning:</h3><p>Use automated scanning tools to identify subdomains at risk of takeover. These tools can help identify misconfigurations and vulnerabilities.</p><h3 id="security-of-external-services">Security of external services:</h3><p>If you use external services requiring subdomains, ensure those services are secure. Regularly monitor and audit the security of these services to prevent potential vulnerabilities.</p><h3 id="dns-rate-limit">DNS rate limit:</h3><p>Implement rate limiting for DNS requests to protect against reconnaissance attacks from potential attackers.</p><h3 id="access-control">Access control:</h3><p>Restrict access to DNS configuration and ensure only authorised personnel can change DNS records.</p><h3 id="vulnerability-disclosure-programs">Vulnerability disclosure programs:</h3><p>Encourage security researchers and users to report subdomain takeover vulnerabilities responsibly. Establish a process for receiving and processing such reports.</p><h3 id="monitor-subdomain-activity">Monitor subdomain activity:</h3><p>Monitor subdomain activity and set up alerts for suspicious or unauthorised changes to DNS records.</p><h2 id="conclusion">Conclusion</h2><p>Subdomain takeovers represent a widespread cybersecurity threat and can significantly impact website owners and users. These takeovers are often due to misconfigured DNS records, abandoned resources, or compromised external services.</p><p>To protect against subdomain takeovers, companies/teams must proactively monitor and secure their subdomains. This includes regular DNS audits, best practices for managing DNS records, and a strong awareness of potential vulnerabilities in external services.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Security</category><media:content url="https://svenruppert.com/images/2023/11/DSC1248_landscape-crop.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2023/11/DSC1248_landscape-crop.jpeg"/><enclosure url="https://svenruppert.com/images/2023/11/DSC1248_landscape-crop.jpeg" type="image/jpeg" length="0"/></item><item><title>Infection Method - Domain Takeover</title><link>https://svenruppert.com/posts/infection-method-domain-takeover/</link><pubDate>Fri, 10 Nov 2023 10:31:25 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/infection-method-domain-takeover/</guid><description>In this post, we will look at another method of infection. These are the attack vectors via domain names. This can happen at the main level, i.e. the domain itself, or via sub-domains. But what exactly is a domain takeover attack?</description><content:encoded>&lt;![CDATA[<p>In this post, we will look at another method of infection. These are the attack vectors via domain names. This can happen at the main level, i.e. the domain itself, or via sub-domains. But what exactly is a domain takeover attack?</p><p>A domain takeover is a cyberattack when an attacker gains control of a domain name owned by another person or organization. This can have severe consequences as the attacker can use the domain for malicious purposes, such as spreading malware, phishing, or taking control of a company&rsquo;s online presence.</p><p>Below, we will look at different ways in which such a takeover can take place:</p><h2 id="1-expired-domain-"><strong>1. Expired domain</strong> :</h2><p>A common possibility of domain takeover is that the owner of a domain forgets to renew it. When a domain registration expires, it may be available for purchase by anyone. Attackers can monitor the expiration of valuable domains and quickly register them as soon as they become available. If you have set up automatic renewal with your domain registrar, this should not happen. But sometimes companies (or people) get rid of old domains. This could be because the acquired company is now fully integrated and the domain is no longer needed, or because a project has expired, or or or. The problem, then, is that these domains may still be included in the internal configurations and firewall rules. The attacker then exploits the extended privileges typically intended for their own systems. If the old domains are now for sale, the attacker has purchased them regularly, and the own configurations still need to be updated, an attack can be carried out.</p><p><a href="https://youtu.be/I5ZX5yX7rOI">https://youtu.be/I5ZX5yX7rOI</a></p><h2 id="2-domain-hijacking-"><strong>2. Domain-Hijacking</strong> :</h2><p>In some cases, attackers can use social engineering techniques to trick domain registrars or Domain Name System (DNS) providers into giving them domain control. This could mean impersonating the domain owner or providing false information to the domain registrar. Here, however, you have to think in different directions. Here is an example of the domain used for a reverse attack on a Maven repository. An open-source project named ABC does not have its associated domain secured. The Maven artefacts are stored in the central Maven Central, for example, under “org.abc”. The attacker now registered the domain abc.org and then contacted the operator of Maven Central, saying that he needed access to his Maven repository. He has lost his access to data. The operator of Maven-Central then asks the applicant to store a TXT entry with a specific code (in this case, it is the ticket number of the request) in the DNS configuration. Once this code is available via DNS, rights to the repository are granted. The attacker can now deposit his Maven artefacts in the repository and thus make them generally available.</p><h2 id="3-dns-misconfiguration-"><strong>3. DNS-Misconfiguration</strong> :</h2><p>Sometimes, domain takeovers occur due to misconfigurations in DNS settings. Attackers can exploit these misconfigurations to gain control of the domain or subdomains and thereby redirect traffic to malicious servers.</p><h3 id="31-what-is-a-critical-dns-misconfiguration">3.1 What is a critical DNS misconfiguration?</h3><p>A critical Domain Name System (DNS) misconfiguration is an error or error in the configuration of a DNS system that can have severe consequences for the availability, security, and functionality of a domain or network. DNS is a crucial Internet component that translates human-readable domain names (like example.com) into IP addresses that computers use to identify and communicate with each other. When DNS misconfiguration occurs, it can lead to various problems, including:</p><h4 id="1-service-interruption-"><strong>1. Service interruption</strong> :</h4><p>A misconfigured DNS record can cause service outages, making a website or other online services inaccessible.</p><h4 id="2-vulnerabilities-"><strong>2. Vulnerabilities</strong> :</h4><p>Misconfigurations can lead to security vulnerabilities, e.g. B., to reveal sensitive information, enable unauthorized access, or enable DNS-related attacks such as DNS cache poisoning or DNS spoofing.</p><h4 id="3-data-loss-"><strong>3. Data Loss</strong> :</h4><p>Incorrect DNS configurations can lead to data loss as changes to DNS records can result in email misdirection or loss of crucial domain-related information.</p><h4 id="4-performance-issues-"><strong>4. Performance Issues</strong> :</h4><p>Suboptimal DNS configurations can slow down domain name resolution and cause delays in website loading or other network activity.</p><h4 id="5-traffic-diversion-"><strong>5. Traffic Diversion</strong> :</h4><p>DNS misconfigurations can inadvertently direct traffic to the wrong IP addresses or locations, potentially leading to data leaks, man-in-the-middle attacks, or other unintended consequences.</p><h4 id="6-domain-hijacking-"><strong>6. Domain-Hijacking</strong> :</h4><p>This is exactly the case of domain takeover considered here, in which unauthorized persons gain control of a domain and the services associated with it. Common examples of critical DNS misconfigurations include errors in DNS records (e.g. A, CNAME, MX, TXT records), incorrect IP address assignments, outdated or expired DNS information, and unauthorized changes to DNS settings .</p><h2 id="4-phishingtheft-of-credentials-"><strong>4. Phishing/Theft Of Credentials</strong> :</h2><p>Attackers can also use phishing attacks to trick domain owners or those with administrative access to domain management accounts into revealing their credentials. Once they have the credentials, they can log in and take control of the domain.</p><h2 id="5-subdomain-takeover-"><strong>5. Subdomain-TakeOver</strong> :</h2><p>A subdomain takeover occurs when an unauthorized person or organization gains control of a subdomain of a domain and thereby effectively takes ownership of it. This unauthorized control can lead to various security risks and potential misuse. The key factors contributing to subdomain takeover are misconfigurations, abandoned resources, and external service integrations.</p><h2 id="6-dns-cache-poisoning-"><strong>6. DNS-Cache-Poisoning</strong> :</h2><p>In some cases, attackers may attempt to poison DNS caches, causing them to resolve a legitimate domain into a malicious IP address. This can lead to a temporary domain takeover as users are redirected to the attacker&rsquo;s server rather than the intended website.</p><p>Once the attacker gains control of a domain, they can use it for various malicious purposes, such as hosting fake websites for phishing attacks, distributing malware, or intercepting communications. This can damage the domain owner&rsquo;s reputation, jeopardize user security and privacy, and result in legal and financial consequences for the legitimate domain owner.</p><h2 id="an-example-from-history">An example from history:</h2><p>A notable historical example of a domain takeover is the case of the Syrian Electronic Army (SEA) in 2013. The SEA was a group of hacktivists who supported the Syrian government and targeted various websites, social media accounts and domains to promote their political agenda. One of the most high-profile incidents involved the takeover of the Twitter accounts and the domain of several well-known media organizations.</p><p>In April 2013, SEA compromised the domain registration account of MarkMonitor, a domain registrar and provider of brand protection services. Using stolen credentials or other means, SEA accessed the New York Times&rsquo; domain registration records. They changed the domain&rsquo;s DNS records, redirecting traffic from the New York Times website to a server controlled by the SEA. As a result, visitors to The New York Times website were greeted with a message from SEA instead of the expected news content.</p><p>This domain takeover disrupted the New York Times&rsquo; online operations and raised concerns about the security of domain registrars. The incident highlighted the importance of protecting domain management accounts and the potential impact of domain takeovers on well-known media organizations and their readership.</p><h2 id="how-do-you-protect-yourself-from-these-attacks">How do you protect yourself from these attacks?</h2><p>To protect yourself from domain takeovers, it is essential for domain owners to:</p><p><strong>1.</strong> Keep your domain registrations current and renew them on time.</p><p><strong>2</strong>. Use strong authentication and authorization mechanisms for domain management accounts.</p><p><strong>3.</strong> Regularly monitor and check your DNS configurations for misconfigurations.</p><p><strong>4.</strong> Educate your team about the risks of social engineering and phishing attacks.</p><p><strong>5.</strong> Use domain security services and technologies to detect and prevent unauthorized changes to domain settings.</p><p>This is not an exhaustive list, but it is a good start to preventing the most common attack vectors.</p><h2 id="conclusion">Conclusion:</h2><p>We have seen many different attack vectors that can lead to a loss of control of the (sub)domain. These attacks are still prevalent and are successfully used even on large companies. Unfortunately, many small companies have exactly these vulnerabilities of abandoned resources in the sub-domain area. I recommend that every project member pay close attention to what else can be actively found in the configurations of the firewalls and DNS, even when it comes to testing and development infrastructure.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>Security</category><media:content url="https://svenruppert.com/images/2023/11/DSC1297_landscape-crop.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2023/11/DSC1297_landscape-crop.jpeg"/><enclosure url="https://svenruppert.com/images/2023/11/DSC1297_landscape-crop.jpeg" type="image/jpeg" length="0"/></item><item><title>EclipseStore High-Performance-Serializer</title><link>https://svenruppert.com/posts/eclipsestore-high-performance-serializer/</link><pubDate>Mon, 09 Oct 2023 21:59:54 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/eclipsestore-high-performance-serializer/</guid><description>I will introduce you to the serializer from the EclipseStore project and show you how to use it to take advantage of a new type of serialization.
Since I learned Java over 20 years ago, I wanted to have a simple solution to serialize Java-Object-Graphs, but without the serialization security and performance issues Java brought us. It should be doable like the following…</description><content:encoded>&lt;![CDATA[<p>I will introduce you to the serializer from the EclipseStore project and show you how to use it to take advantage of a new type of serialization.</p><p>Since I learned Java over 20 years ago, I wanted to have a simple solution to serialize Java-Object-Graphs, but without the serialization security and performance issues Java brought us. It should be doable like the following…</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serializer</span><span class="p">.</span><span class="na">serialize</span><span class="p">(</span><span class="n">objectGraph</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Node</span><span class="w"/><span class="n">objectGraphDeserialized</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serializer</span><span class="p">.</span><span class="na">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">);</span></span></span></code></pre></div></div><p>If you want to know how this is doable with the new Open-Source Project EclipseSerializer? You are right here.</p><p>Before we look at the Open-Source project EclipseStore Serializer, I want to recap a bit of the challenges coming from the Java Serialization itself. This will be the background information to see how powerful the project is.</p><h1 id="java-serialization-in-a-nutshell">Java Serialization in a nutshell</h1><p>Java Serialization is a mechanism provided by the Java programming language that allows you to convert the state of an object into a byte stream. This byte stream can be easily stored in a file, sent over a network, or otherwise persisted. Later, you can deserialize the byte stream to reconstruct the original object, effectively saving and restoring the object&rsquo;s state.</p><p>Here are some key points about Java Serialization:</p><p><strong>Serializable Interface:</strong> To make a Java class serializable, it needs to implement the<code>Serializable</code> interface. This interface doesn&rsquo;t have any methods; it acts as a marker interface to indicate that the objects of this class can be serialized.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kn">import</span><span class="w"/><span class="nn">java.io.Serializable</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MyClass</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">Serializable</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// class members and methods</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Serialization Process:</strong> To serialize an object, you typically use<code>ObjectOutputStream</code>. You create an instance of this class and write your object to it. For example:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">FileOutputStream</span><span class="w"/><span class="n">fileOut</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileOutputStream</span><span class="p">(</span><span class="s">"object.ser"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ObjectOutputStream</span><span class="w"/><span class="n">out</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectOutputStream</span><span class="p">(</span><span class="n">fileOut</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">MyClass</span><span class="w"/><span class="n">obj</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">MyClass</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">out</span><span class="p">.</span><span class="na">writeObject</span><span class="p">(</span><span class="n">obj</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Deserialization Process:</strong> To deserialize an object, you use<code>ObjectInputStream</code>. You read the byte stream from a file or network and then use<code>ObjectInputStream</code> to recreate the object.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="k">try</span><span class="w"/><span class="p">(</span><span class="n">FileInputStream</span><span class="w"/><span class="n">fileIn</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">FileInputStream</span><span class="p">(</span><span class="s">"object.ser"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ObjectInputStream</span><span class="w"/><span class="n">in</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ObjectInputStream</span><span class="p">(</span><span class="n">fileIn</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">MyClass</span><span class="w"/><span class="n">obj</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">MyClass</span><span class="p">)</span><span class="w"/><span class="n">in</span><span class="p">.</span><span class="na">readObject</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Now 'obj' contains the deserialized object</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">catch</span><span class="w"/><span class="p">(</span><span class="n">IOException</span><span class="w"/><span class="o">|</span><span class="w"/><span class="n">ClassNotFoundException</span><span class="w"/><span class="n">e</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">e</span><span class="p">.</span><span class="na">printStackTrace</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Versioning Considerations:</strong> If you change a serialised class&rsquo;s structure (e.g., adding or removing fields or changing their types), the deserialization of old serialized objects may fail. Java provides mechanisms like serialVersionUID to help with versioning and compatibility.</p><p><strong>Security Considerations:</strong> Serialization can be a security risk, especially if you deserialize data from untrusted sources. Malicious code can be executed during deserialization. To mitigate this risk, you should carefully validate and sanitize any data you deserialize or consider alternative serialization mechanisms like JSON or XML.</p><p><strong>Custom Serialization:</strong> In your class, you can customize the serialization and deserialization process by providing<code>writeObject</code> and<code>readObject</code> methods. These methods allow you to control how the object&rsquo;s state is written to and read from the byte stream.</p><p>In summary, Java Serialization is a valuable feature for persisting objects and sending them across a network. However, it comes with some challenges related to versioning and security, so it should be used cautiously, especially when dealing with untrusted data sources.</p><h2 id="what-are-the-security-issues">What are the security issues</h2><p>Java Serialization can introduce several security issues, particularly when deserializing data from untrusted sources. Here are some of the security concerns associated with Java Serialization:</p><p><strong>Remote Code Execution:</strong> One of the most significant security risks with Java Serialization is the potential for remote code execution. When you deserialize an object, the Java runtime system can execute arbitrary code contained within the serialized data. Attackers can exploit this to execute malicious code on the target system. This vulnerability can lead to serious security breaches.</p><p><strong>Denial of Service (DoS):</strong> An attacker can create a serialized object with a large size, causing excessive memory consumption and potentially leading to a denial of service attack. Deserializing large objects can consume significant CPU and memory resources, slowing down or crashing the application.</p><p><strong>Data Tampering:</strong> Serialized data can be tampered with during transmission or storage. Attackers can modify the serialized byte stream to alter the state of the deserialized object or introduce vulnerabilities.</p><p><strong>Insecure Deserialization:</strong> Deserializing untrusted data without proper validation can lead to security issues. For example, if a class that performs sensitive operations is deserialized from untrusted input, an attacker can manipulate the object&rsquo;s state to perform unauthorized actions.</p><p><strong>Information Disclosure:</strong> When objects are serialized, sensitive information may be included in the serialized form. If this data is not adequately protected or encrypted, an attacker may gain access to sensitive information.</p><h2 id="how-to-mitigate-serialization-issues">How To Mitigate Serialization Issues</h2><p>To mitigate these security issues, consider the following best practices:</p><p><strong>Avoid Deserializing Untrusted Data:</strong> If possible, avoid deserializing data from untrusted sources altogether. Instead, use safer data interchange formats like JSON or XML for untrusted data. (Or use the EclipseSerializer ;-) )</p><p><strong>Implement Input Validation:</strong> When deserializing data, validate and sanitize the input to ensure it adheres to expected data structures and doesn&rsquo;t contain unexpected or malicious data.</p><p><strong>Use Security Managers:</strong> Java&rsquo;s Security Manager can be used to restrict the permissions and actions of deserialized code. However, it&rsquo;s important to note that Security Managers have been deprecated in newer versions of Java.</p><p><strong>Whitelist Classes:</strong> Limit the classes that can be deserialized to a predefined set of trusted classes. This can help prevent the deserialization of arbitrary and potentially malicious classes.</p><p><strong>Versioning and Compatibility:</strong> Be cautious when making changes to serialized classes. Use<code>serialVersionUID</code> to manage versioning and compatibility between different versions of serialized objects.</p><p><strong>Security Libraries:</strong> Consider using third-party libraries like Apache Commons Collections or OWASP Java Serialization Security (Java-Serial-Killer) to help mitigate known vulnerabilities and prevent common attacks.</p><p>In summary, Java Serialization can introduce serious security risks, especially when dealing with untrusted data. It&rsquo;s essential to take precautions, validate inputs, and consider alternative serialization methods or libraries to enhance security. Additionally, keeping your Java runtime environment up to date is crucial, as newer versions of Java may include security improvements and fixes for known vulnerabilities.</p><h2 id="why-is-json-or-xml-not-the-perfect-solution-for-the-jvm">Why is JSON or XML not the perfect solution for the JVM?</h2><p>Many papers and lectures recommend circumventing the security risks of serialization by using XML or JSON. This is a structured representation of the data that is to be transferred. There are also security problems, but I will address these in a separate article. However, what should be addressed are two things. First, the data must be converted into a text representation. This usually requires more data volume than with a pure binary model. In addition, data such as the binary data of images must be recoded so that only printable or UTF-8 characters can be transmitted. This process requires a lot of time and usually a lot of memory when transforming it into XML and back from XML into the original format.</p><p>The second point that causes problems in most cases is the data structure. In XML and JSON, object references can only be stored in a more manageable manner. This makes processing many times more complicated, slower and more resource-intensive. Even though many solid solutions can be used to convert Java objects into XML or JSON, I recommend looking for new approaches occasionally.</p><h1 id="eclipsestore---serializer---practical-part">EclipseStore - Serializer - Practical Part</h1><p>Now, let&rsquo;s get to the practical stuff in this article. The dependency is needed first. To do this, we add the following instructions to the pom.xml. The first release was prepared when writing this article, and a SNAPSHOT version (1.0.0-SNAPSHOT) was available from the repositories. In this case, you still have to use the SNAPSHOT repositories (<a href="https://oss.sonatype.org/content/repositories/snapshots">https://oss.sonatype.org/content/repositories/snapshots</a>)</p><p>Definition inside the pom.xml.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;groupId&gt;</span>org.eclipse.serializer<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;artifactId&gt;</span>serializer<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;version&gt;</span>{maven-version-number}<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span></code></pre></div></div><p>The rest will happen quickly once we&rsquo;re ready and have fetched the dependency. For the first test, we created a class called Node. Each Node can have a right and a left child. With this, we can create a tree.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">id</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Node</span><span class="w"/><span class="n">leftNode</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">Node</span><span class="w"/><span class="n">rightNode</span><span class="p">;</span></span></span></code></pre></div></div><p>As an example, I created the following construct and then serialized and de-serialized it once using the serializer.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Node</span><span class="w"/><span class="n">rootNode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"rootNode"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Node</span><span class="w"/><span class="n">leftChildLev01</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-L"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Node</span><span class="w"/><span class="n">rightChildLev01</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-R"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">leftChildLev01</span><span class="p">.</span><span class="na">addLeft</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-L-R"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">leftChildLev01</span><span class="p">.</span><span class="na">addLeft</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-L-L"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rightChildLev01</span><span class="p">.</span><span class="na">addLeft</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-R-L"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rightChildLev01</span><span class="p">.</span><span class="na">addRight</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-R-R"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">addLeft</span><span class="p">(</span><span class="n">leftChildLev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">addRight</span><span class="p">(</span><span class="n">rightChildLev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Serializer</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[]&gt;</span><span class="w"/><span class="n">serializer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Serializer</span><span class="p">.</span><span class="na">Bytes</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serializer</span><span class="p">.</span><span class="na">serialize</span><span class="p">(</span><span class="n">rootNode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Node</span><span class="w"/><span class="n">rootNodeDeserialized</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serializer</span><span class="p">.</span><span class="na">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">rootNode</span><span class="p">.</span><span class="na">toString</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">" ========== "</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">rootNodeDeserialized</span><span class="p">.</span><span class="na">toString</span><span class="p">());</span></span></span></code></pre></div></div><p>Now, let&rsquo;s see whether this also works with a Java object graph. To do this, the class representing the node is changed so that a father node can also be defined. Cycles can now be set up.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class GraphNode {</span></span><span class="line"><span class="cl"> private String id;</span></span><span class="line"><span class="cl"> private GraphNode parent;</span></span><span class="line"><span class="cl"> private List<span class="nt">&lt;GraphNode&gt;</span> childGraphNodes = new ArrayList<span class="err">&lt;</span>&gt;();</span></span></code></pre></div></div><p>We take the graph listed here as an example.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">rootNode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"rootNode"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child01Lev01</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child01Lev01"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child02Lev01</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child02Lev01"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child01Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child02Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev01</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">rootNode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child02Lev01</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">rootNode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child01Lev02</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child01Lev02"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child02Lev02</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child02Lev02"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev01</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child01Lev02</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev01</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child02Lev02</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev02</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">child01Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev02</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">child01Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child01Lev03</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child01Lev03"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child02Lev03</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child02Lev03"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev03</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">child02Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child02Lev03</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">child02Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child02Lev01</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child01Lev03</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child02Lev01</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child02Lev03</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">//creating cycles</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child01Lev03</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">child02Lev03</span><span class="p">);</span></span></span></code></pre></div></div><p>This graph is also processed without any problems, without the cycles causing any issues.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Serializer</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[]&gt;</span><span class="w"/><span class="n">serializer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Serializer</span><span class="p">.</span><span class="na">Bytes</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serializer</span><span class="p">.</span><span class="na">serialize</span><span class="p">(</span><span class="n">rootNode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">rootNodeDeserialized</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serializer</span><span class="p">.</span><span class="na">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">);</span></span></span></code></pre></div></div><p>You can make the examples even more complex and try out the subtleties of inheritance. New data types from JDK17 are also supported. This means I have a potent tool to handle various tasks. For example, one use can be found in another Eclipse project called EclipseStore. A persistence mechanism is provided here based on this serialization. But your own small projects can also benefit from this. I will show how quickly this can be integrated into a residual service.</p><h2 id="building-a-simple-rest-service">Building A Simple REST Service</h2><p>If you want to create a simple REST service for transferring byte streams in Java without using Spring Boot, you can use the Java SE API and the HttpServer class from the com.sun.net.httpserver package, which allows you to create an HTTP server.</p><ul><li>We create an HTTP server on port 8080 and define a context for handling requests to &ldquo;/api/bytestream.&rdquo;</li><li>The ByteStreamHandler class handles both POST requests for uploading byte streams and GET requests for downloading byte streams.</li><li>For POST requests, it reads the incoming byte stream, processes it as needed, and sends a response.</li><li>For GET requests, it sends a predefined byte stream as a response.</li></ul><p>Remember that this is a simple example, and you can expand upon it to handle more complex use cases and error handling as needed for your specific application. Also, note that the com.sun.net.httpserver package is part of the JDK, but it may not be available in all Java distributions.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">ByteStreamHandler</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HttpHandler</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handle</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">exchange</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">requestMethod</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestMethod</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">requestMethod</span><span class="p">.</span><span class="na">equalsIgnoreCase</span><span class="p">(</span><span class="s">"POST"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Handle POST requests for uploading byte streams</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">handleUpload</span><span class="p">(</span><span class="n">exchange</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/><span class="k">else</span><span class="w"/><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">requestMethod</span><span class="p">.</span><span class="na">equalsIgnoreCase</span><span class="p">(</span><span class="s">"GET"</span><span class="p">))</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Handle GET requests for downloading byte streams</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">handleDownload</span><span class="p">(</span><span class="n">exchange</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handleUpload</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">exchange</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Get the input stream from the request</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">InputStream</span><span class="w"/><span class="n">inputStream</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getRequestBody</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Read the byte stream and process it as needed</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">ByteArrayOutputStream</span><span class="w"/><span class="n">byteArrayOutputStream</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">ByteArrayOutputStream</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">buffer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="kt">byte</span><span class="o">[</span><span class="n">1024</span><span class="o">]</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">bytesRead</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">while</span><span class="w"/><span class="p">((</span><span class="n">bytesRead</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">inputStream</span><span class="p">.</span><span class="na">read</span><span class="p">(</span><span class="n">buffer</span><span class="p">))</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="o">-</span><span class="n">1</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">byteArrayOutputStream</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span><span class="w"/><span class="n">0</span><span class="p">,</span><span class="w"/><span class="n">bytesRead</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Process the uploaded byte stream</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// (e.g., save to a file or perform other actions)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">byteArrayOutputStream</span><span class="p">.</span><span class="na">toByteArray</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Do something with 'data'</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Serializer</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[]&gt;</span><span class="w"/><span class="n">serializer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Serializer</span><span class="p">.</span><span class="na">Bytes</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">GraphNode</span><span class="w"/><span class="n">deserialized</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serializer</span><span class="p">.</span><span class="na">deserialize</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//process the data</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"deserialized = "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">deserialized</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Send a response (you can customize this)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">response</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Byte stream uploaded successfully."</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">200</span><span class="p">,</span><span class="w"/><span class="n">response</span><span class="p">.</span><span class="na">length</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseBody</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="na">getBytes</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">os</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">handleDownload</span><span class="p">(</span><span class="n">HttpExchange</span><span class="w"/><span class="n">exchange</span><span class="p">)</span><span class="w"/><span class="kd">throws</span><span class="w"/><span class="n">IOException</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// Simulate generating and sending a byte stream as a response</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">String</span><span class="w"/><span class="n">response</span><span class="w"/><span class="o">=</span><span class="w"/><span class="s">"Hello, Byte Stream!"</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Serializer</span><span class="o">&lt;</span><span class="kt">byte</span><span class="o">[]&gt;</span><span class="w"/><span class="n">serializer</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Serializer</span><span class="p">.</span><span class="na">Bytes</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kt">byte</span><span class="o">[]</span><span class="w"/><span class="n">data</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">serializer</span><span class="p">.</span><span class="na">serialize</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="na">getBytes</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">sendResponseHeaders</span><span class="p">(</span><span class="n">200</span><span class="p">,</span><span class="w"/><span class="n">data</span><span class="p">.</span><span class="na">length</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">OutputStream</span><span class="w"/><span class="n">os</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">exchange</span><span class="p">.</span><span class="na">getResponseBody</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">os</span><span class="p">.</span><span class="na">write</span><span class="p">(</span><span class="n">data</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">os</span><span class="p">.</span><span class="na">close</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h1 id="conclusion">Conclusion:</h1><p>We&rsquo;ve looked at the typical problems with Java&rsquo;s original serialization and how cumbersome the implementation is to use. The detour via JSON and XML is unnecessary when communicating from JVM to JVM using the open-source Eclipse Serializer project. There are no restrictions when it comes to modelling a graph, as not only are the current new data types up to and including JDK17 already processed, but cycles within the graph are also no problem.</p><p>Using the Serializable interface is also unnecessary and does not influence processing. The easy handling allows it to be used even in tiny projects such as the REST service shown here using on-board JDK resources. A larger project that uses the serializer is the open-source project EclipseStore. A high-performance persistence mechanism for the JVM is offered here.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><category>Security</category><media:content url="https://svenruppert.com/images/2023/10/DSC1673_landscape-crop.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2023/10/DSC1673_landscape-crop.jpeg"/><enclosure url="https://svenruppert.com/images/2023/10/DSC1673_landscape-crop.jpeg" type="image/jpeg" length="0"/></item><item><title>EclipseStore - Storing more complex data structures</title><link>https://svenruppert.com/posts/eclipsestore-storing-more-complex-data-structures/</link><pubDate>Fri, 06 Oct 2023 12:22:50 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/eclipsestore-storing-more-complex-data-structures/</guid><description>How to store complex data structures using EclipseStore? Are there any restrictions? How can you work more efficiently on such structures? We will now get to the bottom of these questions here.</description><content:encoded>&lt;![CDATA[<p>How to store complex data structures using EclipseStore? Are there any restrictions? How can you work more efficiently on such structures? We will now get to the bottom of these questions here.</p><p>In the first part of my series, I showed how to prepare EclipseStore for use in a project. We also initialized the StorageManager and saved, modified and deleted the first data. But what about more complex structures? Can you use inheritance? To do this, we will now create a small class model.</p><p>First, let&rsquo;s look at what inheritance looks like. To do this, we take an interface called<strong>BaseInterfaceA</strong> , an implementation<strong>BaseClassA</strong> and a derivative<strong>LevelOneA</strong>. We will now try to save this and see how it behaves depending on the input when saving.</p><p>Here is the listing of the classes and interfaces involved. Since we already saw in the first part that the implementation variant has no influence, I will use a standard implementation regarding getters and setters or the constructors.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">interface</span><span class="nc">BaseInterfaceA</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="k">default</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">allUpperCase</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">value</span><span class="p">){</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="w"/><span class="n">value</span><span class="p">.</span><span class="na">toUpperCase</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">BaseClassA</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">BaseInterfaceA</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">valueBaseA</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="w"/><span class="s">"BaseClassA{"</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="s">"valueBaseA='"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">valueBaseA</span><span class="w"/><span class="o">+</span><span class="w"/><span class="sc">'\''</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="sc">'}'</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">equals</span><span class="p">(</span><span class="n">Object</span><span class="w"/><span class="n">o</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="k">this</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">o</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">o</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">getClass</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="n">o</span><span class="p">.</span><span class="na">getClass</span><span class="p">())</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">BaseClassA</span><span class="w"/><span class="n">that</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">BaseClassA</span><span class="p">)</span><span class="w"/><span class="n">o</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">valueBaseA</span><span class="p">,</span><span class="w"/><span class="n">that</span><span class="p">.</span><span class="na">valueBaseA</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="nf">hashCode</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">hash</span><span class="p">(</span><span class="n">valueBaseA</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">getValueBaseA</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="w"/><span class="n">valueBaseA</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setValueBaseA</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">valueBaseA</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">this</span><span class="p">.</span><span class="na">valueBaseA</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">valueBaseA</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">LevelOneA</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">BaseClassA</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">valueOneA</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="nf">LevelOneA</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">valueOneA</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">this</span><span class="p">.</span><span class="na">valueOneA</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">valueOneA</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kt">boolean</span><span class="w"/><span class="nf">equals</span><span class="p">(</span><span class="n">Object</span><span class="w"/><span class="n">o</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="k">this</span><span class="w"/><span class="o">==</span><span class="w"/><span class="n">o</span><span class="p">)</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">true</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="n">o</span><span class="w"/><span class="o">==</span><span class="w"/><span class="kc">null</span><span class="w"/><span class="o">||</span><span class="w"/><span class="n">getClass</span><span class="p">()</span><span class="w"/><span class="o">!=</span><span class="w"/><span class="n">o</span><span class="p">.</span><span class="na">getClass</span><span class="p">())</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">if</span><span class="w"/><span class="p">(</span><span class="o">!</span><span class="kd">super</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">o</span><span class="p">))</span><span class="w"/><span class="k">return</span><span class="w"/><span class="kc">false</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="n">LevelOneA</span><span class="w"/><span class="n">levelOneA</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">LevelOneA</span><span class="p">)</span><span class="w"/><span class="n">o</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">equals</span><span class="p">(</span><span class="n">valueOneA</span><span class="p">,</span><span class="w"/><span class="n">levelOneA</span><span class="p">.</span><span class="na">valueOneA</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="nf">hashCode</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="w"/><span class="n">Objects</span><span class="p">.</span><span class="na">hash</span><span class="p">(</span><span class="kd">super</span><span class="p">.</span><span class="na">hashCode</span><span class="p">(),</span><span class="w"/><span class="n">valueOneA</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">getValueOneA</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="w"/><span class="n">valueOneA</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setValueOneA</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">valueOneA</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">this</span><span class="p">.</span><span class="na">valueOneA</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">valueOneA</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">       </span><span class="k">return</span><span class="w"/><span class="s">"LevelOneA{"</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="s">"valueOneA='"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">valueOneA</span><span class="w"/><span class="o">+</span><span class="w"/><span class="sc">'\''</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">               </span><span class="sc">'}'</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w">   </span><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><h2 id="case-i-direct-saving-of-the-respective-classes">Case I: Direct saving of the respective classes</h2><p>The entities are packed directly into a list and saved in the first case. As we can see, there are no special features here. This is the same case as the first article. The output of the<strong>printElements</strong> method is exactly what we expected.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public static void main(String[] args) {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   EmbeddedStorageManager storageManager = EmbeddedStorage.start();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   storageManager.setRoot(new ArrayList<span class="nt">&lt;LevelOneA&gt;</span>());</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   storageManager.storeRoot();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   List<span class="nt">&lt;LevelOneA&gt;</span> root = (List<span class="nt">&lt;LevelOneA&gt;</span>) storageManager.root();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   root.add(new LevelOneA("levelOneA - 01"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   root.add(new LevelOneA("levelOneA - 02"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   root.add(new LevelOneA("levelOneA - 03"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   storageManager.storeRoot();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   storageManager.shutdown();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   storageManager = EmbeddedStorage.start();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   List<span class="nt">&lt;LevelOneA&gt;</span> rootLoaded = (List<span class="nt">&lt;LevelOneA&gt;</span>) storageManager.root();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   rootLoaded.forEach(System.out::println);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   System.out.println(" ========== ");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">   storageManager.shutdown();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><div class="pull-quote"><p>console output:</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 01&rsquo;}</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 02&rsquo;}</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 03&rsquo;}</p><p>==========</p></div><h2 id="case-ii-save-as-a-base-class">Case II: Save as a base class</h2><p>Now, let&rsquo;s consider the case where a class is passed to the StorageManager for storage as one of its base classes. In this case, the<strong>LevelOneA</strong> class is passed as the base class<strong>BaseClassA</strong>. It should be noted that this primary type was also used when defining the root list.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">EmbeddedStorageManager storageManager = EmbeddedStorage.start();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager.setRoot(new ArrayList<span class="nt">&lt;BaseClassA&gt;</span>());</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager.storeRoot();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">List<span class="nt">&lt;BaseClassA&gt;</span> root = (List<span class="nt">&lt;BaseClassA&gt;</span>) storageManager.root();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">root.add(new LevelOneA("levelOneA - 01"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">root.add(new LevelOneA("levelOneA - 02"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">root.add(new LevelOneA("levelOneA - 03"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager.storeRoot();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager.shutdown();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager = EmbeddedStorage.start();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">List<span class="nt">&lt;BaseClassA&gt;</span> rootLoaded = (List<span class="nt">&lt;BaseClassA&gt;</span>) storageManager.root();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">rootLoaded.forEach(System.out::println);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">System.out.println(" ========== ");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager.shutdown();</span></span></code></pre></div></div><div class="pull-quote"><p>The output again meets our expectations.</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 01&rsquo;}</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 02&rsquo;}</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 03&rsquo;}</p><p>==========</p></div><h2 id="case-ii-saving-an-implementation-as-an-interface">Case II: Saving an implementation as an interface</h2><p>Now let&rsquo;s move on to the case in which we proceed via the interface. There are no problems here either. Everything behaves as expected.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">EmbeddedStorageManager storageManager = EmbeddedStorage.start();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager.setRoot(new ArrayList<span class="nt">&lt;BaseInterfaceA&gt;</span>());</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager.storeRoot();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">List<span class="nt">&lt;BaseInterfaceA&gt;</span> root = (List<span class="nt">&lt;BaseInterfaceA&gt;</span>) storageManager.root();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">root.add(new LevelOneA("levelOneA - 01"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">root.add(new LevelOneA("levelOneA - 02"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">root.add(new LevelOneA("levelOneA - 03"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager.storeRoot();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager.shutdown();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager = EmbeddedStorage.start();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">List<span class="nt">&lt;BaseInterfaceA&gt;</span> rootLoaded = (List<span class="nt">&lt;BaseInterfaceA&gt;</span>) storageManager.root();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">rootLoaded.forEach(System.out::println);</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">System.out.println(" ========== ");</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl">storageManager.shutdown();</span></span></code></pre></div></div><div class="pull-quote"><p>The output on the console is still unchanged.</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 01&rsquo;}</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 02&rsquo;}</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 03&rsquo;}</p><p>==========</p></div><h2 id="case-iv-saving-a-mixed-list-via-a-common-interface">Case IV: Saving a mixed list via a common interface</h2><p>Now if we define a list of type<strong>BaseInterfaceA</strong> as root and then add instances of different implementations. How does EclipseStore behave during the save process? To make a long story short, it works as intended. All instances are stored neatly after deployment. So we have no loss of information.</p><div class="pull-quote"><p>The console output will then look like this.</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 01&rsquo;}</p><p>BaseClassA{valueBaseA=&lsquo;BaseClassA - 02&rsquo;}</p><p>LevelOneA{valueOneA=&lsquo;levelOneA - 03&rsquo;}</p><p>==========</p></div><h2 id="storing-lists-trees-and-graphs">Storing lists, trees and graphs</h2><p>Now that we&rsquo;ve looked at whether inheritance is supported as much as we hoped, we&rsquo;re getting to the point where we can start thinking about the data structures themselves. We have seen that simple entities and lists of entities are relatively easy for EclipseStore. Now, let&rsquo;s go one step further and look at trees and graphs.</p><h3 id="storing-of-trees">Storing of trees</h3><p>In computer science, we understand a tree to be a data structure that can have branches but does not contain any cycles. This makes them a special form of graphs, just as lists are a special form of trees. So, let&rsquo;s come to a tree implementation in which a node can have two child nodes. Of course, you can also build this structure with n child nodes, but this will not bring any added value in our case. If we create such a tree and give the root node to the StorageManager, we can save this tree without any further action. Modifying individual elements also works as usual. You can also change the desired segment and save on this or any higher-level piece.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">EmbeddedStorageManager</span><span class="w"/><span class="n">storageManager</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">EmbeddedStorage</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Node</span><span class="w"/><span class="n">rootNode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"rootNode"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">setRoot</span><span class="p">(</span><span class="n">rootNode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">storeRoot</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Node</span><span class="w"/><span class="n">leftChildLev01</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-L"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Node</span><span class="w"/><span class="n">rightChildLev01</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-R"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">leftChildLev01</span><span class="p">.</span><span class="na">addLeft</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-L-R"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">leftChildLev01</span><span class="p">.</span><span class="na">addLeft</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-L-L"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rightChildLev01</span><span class="p">.</span><span class="na">addLeft</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-R-L"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rightChildLev01</span><span class="p">.</span><span class="na">addRight</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Node</span><span class="p">(</span><span class="s">"Root-R-R"</span><span class="p">));</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">addLeft</span><span class="p">(</span><span class="n">leftChildLev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">addRight</span><span class="p">(</span><span class="n">rightChildLev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">storeRoot</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">shutdown</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">EmbeddedStorage</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">Node</span><span class="w"/><span class="n">rootLoaded</span><span class="w"/><span class="o">=</span><span class="w"/><span class="p">(</span><span class="n">Node</span><span class="p">)</span><span class="w"/><span class="n">storageManager</span><span class="p">.</span><span class="na">root</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="n">rootLoaded</span><span class="p">.</span><span class="na">toString</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">" ========== "</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">shutdown</span><span class="p">();</span></span></span></code></pre></div></div><div class="pull-quote"><p>Output to the console (subsequently formatted for readability):</p><p>Node{id=&lsquo;rootNode&rsquo;,</p><p>leftNode=Node{id=&lsquo;Root-L&rsquo;,</p><p>leftNode=Node{id=&lsquo;Root-L-L&rsquo;,</p><p>leftNode=null,</p><p>rightNode=null</p><p>},</p><p>rightNode=null</p><p>},</p><p>rightNode=Node{id=&lsquo;Root-R&rsquo;,</p><p>leftNode=Node{id=&lsquo;Root-R-L&rsquo;,</p><p>leftNode=null,</p><p>rightNode=null</p><p>},</p><p>rightNode=Node{id=&lsquo;Root-R-R&rsquo;,</p><p>leftNode=null,</p><p>rightNode=null</p><p>}</p><p>}</p><p>}</p></div><h3 id="storing-graphs">Storing graphs</h3><p>Unfortunately, you don&rsquo;t just have to deal with lists and trees. Complex data models often have cycles. This can come about, for example, through bidirectional relationships. Unwanted cycles can also arise in a model if it grows over time and is repeatedly expanded. Whether these cycles are tightly or loosely coupled plays a minor role. The following example creates a chart that contains multiple cycles. Can the diagram then be saved, modified and loaded? Does EclipseStore recognize these structures and can resolve or break the loops?</p><p>In this example, the graph consists of nodes of the<strong>GraphNode</strong> class. This class contains an attribute of type String to store an ID. There is also a reference to the parent node and a list of child nodes. This means you can now create any nested chart you want.</p><p>In this case, the graphic looks like this.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">rootNode</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"rootNode"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child01Lev01</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child01Lev01"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child02Lev01</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child02Lev01"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child01Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child02Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev01</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">rootNode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child02Lev01</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">rootNode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child01Lev02</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child01Lev02"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child02Lev02</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child02Lev02"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev01</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child01Lev02</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev01</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child02Lev02</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev02</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">child01Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev02</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">child01Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child01Lev03</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child01Lev03"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">GraphNode</span><span class="w"/><span class="n">child02Lev03</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">GraphNode</span><span class="p">(</span><span class="s">"child02Lev03"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child01Lev03</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">child02Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child02Lev03</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">child02Lev01</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child02Lev01</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child01Lev03</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">child02Lev01</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child02Lev03</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="c1">//creating cycles</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">addChildGraphNode</span><span class="p">(</span><span class="n">child01Lev03</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">rootNode</span><span class="p">.</span><span class="na">setParent</span><span class="p">(</span><span class="n">child02Lev03</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">EmbeddedStorageManager</span><span class="w"/><span class="n">storageManager</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">EmbeddedStorage</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">setRoot</span><span class="p">(</span><span class="n">rootNode</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">storeRoot</span><span class="p">();</span></span></span></code></pre></div></div><p>The node rootNode is passed to the StorageManager as root and saved. A subsequent loading of the root results in the previously created graph.</p><figure><img src="/images/2023/10/image.png" alt="" loading="lazy" decoding="async"/><p>As we can see from this example, EclipseStore can store graphs without any problems. Cycles are acceptable here.</p><h2 id="conclusion">Conclusion:</h2><p>We have now seen that EclipseStore can store any data structure in its entirety; lists, trees and graphs are not a limitation. This allows us to create the data model independently of the persistence layer. This is a possibility that I have always been looking for in the last 20 years of my Java activities.</p><p>We will now deal with the intricacies of EclipseStore in the following parts. It remains exciting.</p><p>Happy coding</p><p>Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><media:content url="https://svenruppert.com/images/2023/10/DSC1511_landscape-crop.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2023/10/DSC1511_landscape-crop.jpeg"/><enclosure url="https://svenruppert.com/images/2023/10/DSC1511_landscape-crop.jpeg" type="image/jpeg" length="0"/></item><item><title>How to start with EclipseStore - 01</title><link>https://svenruppert.com/posts/how-to-start-with-eclipsestore-01/</link><pubDate>Wed, 04 Oct 2023 10:00:04 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/how-to-start-with-eclipsestore-01/</guid><description>We will take the first steps with the Eclipse Store here. I will show what the Eclipse Store is, how you can integrate it into your project and what the first steps look like from a developer&rsquo;s perspective. All in all, it is a practical introduction.</description><content:encoded>&lt;![CDATA[<p>We will take the first steps with the Eclipse Store here. I will show what the Eclipse Store is, how you can integrate it into your project and what the first steps look like from a developer&rsquo;s perspective. All in all, it is a practical introduction.</p><h2 id="what-is-eclipse-store">What is Eclipse Store?</h2><p>First, I would like to briefly explain what Eclipse Store actually is. Simply put, this is a mechanism for storing Java object trees. This initially sounds very theoretical, but it is much simpler than you might think.</p><p>When I started developing Java applications in 1996, I dreamed of being able to easily store the objects I created in my application. I found the assumption that serialization could be used to write everything to the hard drive as a byte stream very good. Unfortunately, the reality looked different or still looks different today. You were quickly confronted with various technologies, all with their own characteristics. I still remember very clearly the first steps in which the application&rsquo;s data had to be persisted via a JDBC interface. Who doesn&rsquo;t know the JDBC-ODBC interface from back then? A typical application now had JDBC connections, mapping layers that implement everything in SQL and, of course, caches in various places so that the application&rsquo;s responsiveness remained tolerable for the user. It&rsquo;s not easy when dealing with a more complex data model.</p><p>With Eclipse Store, we now have a tool that eliminates most of these technology layers. So we have finally achieved what was promised to us in 1996.</p><p>Let&rsquo;s now come to the implementation of our own project. For this, we need the maven dependencies. For this example, I used the first final version. This is based on Microstream Version 8 and is the basis for the Eclipse project. As far as I know, Microstream is not being actively developed further.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;dependencies&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;dependency&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;groupId&gt;</span>org.eclipse.store<span class="nt">&lt;/groupId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;artifactId&gt;</span>storage-embedded<span class="nt">&lt;/artifactId&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;version&gt;</span>{maven-version}<span class="nt">&lt;/version&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependency&gt;</span></span></span><span class="line"><span class="cl"><span class="nt">&lt;/dependencies&gt;</span></span></span></code></pre></div></div><p>Now that we have added the necessary dependencies to the project, we can start with the first lines of source code.</p><h2 id="basic-first-steps">Basic first Steps:</h2><h3 id="the-storage-manager">The Storage Manager:</h3><p>We will now look at the essential elements of architecture. The linchpin of the persistence solution is the &ldquo;Storage Manager&rdquo;. This is the interface through which the behaviour can be configured. All entities are also transferred to persistence, retrieved and deleted. The storage manager itself is available in various forms. We will focus on embedded storage here for now. This implementation is an in-memory solution that accesses the local hard drive directly. You shouldn&rsquo;t let the name confuse you. As is usual with RDBMS systems, it is not a tiny solution that allows you to do a little testing. The implementation of embedded storage is absolutely suitable for production.</p><p>So, an instance of this implementation is needed. The easiest way to get this is to call:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="o">**</span><span class="kd">final</span><span class="o">****</span><span class="n">EmbeddedStorageManager</span><span class="w"/><span class="n">storageManager</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">EmbeddedStorage</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="o">**</span></span></span></code></pre></div></div><p>The chosen implementation depends on which dependencies have been defined in the pom.xml. In our case, it&rsquo;s the embedded implementation. We will discuss various other implementations in the following parts. We will also take a closer look at how the behaviour can be configured later. These parameters are irrelevant for the first steps and can safely be ignored.</p><h3 id="storage-root">Storage Root:</h3><p>The storage manager now needs to know the root of the graphs to be stored. You can imagine this as described below.</p><p>If we only have one element that needs to be stored, then no further wrapper is necessary. However, if we have two parts that we want to save, we need a container to hold both elements. This container is then the root through which initial access to all elements can occur. This can be a list or a class specifically tailored to your needs. This container must now be made available for persistence.</p><p>Let&rsquo;s look at an example. Two different strings should be stored here. A list is used as a container. This means that the instance of the list is the StorageRoot element. This must now be declared as an anchor point.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">EmbeddedStorageManager</span><span class="w"/><span class="n">storageManager</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">EmbeddedStorage</span><span class="p">.</span><span class="na">start</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">storageManager</span><span class="p">.</span><span class="na">setRoot</span><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">ArrayList</span><span class="o">&lt;&gt;</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">storageManager</span><span class="p">.</span><span class="na">storeRoot</span><span class="p">();</span></span></span></code></pre></div></div><p>The “storeRoot()” instruction causes this modification to be saved at the root. We will see this “storeRoot()” method call more often. This persists in all changes in the graph, starting from the root. In our case, the root itself is saved.</p><p>How can another element be added to this container? There is no need to keep a reference to this container. Instead, we can ask the Storage Manager for this reference. What should not be forgotten at this point? If you overwrite the root, all previously saved elements will be deleted. These are then no longer under the supervision of persistence.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">**List<span class="nt">&lt;String&gt;</span> root = (List<span class="nt">&lt;String&gt;</span>) storageManager.root();**</span></span></code></pre></div></div><p>A downer at this point. Unfortunately, the &ldquo;root()&rdquo; method is not typed. Here, you have to know exactly what type the container is. We&rsquo;ll look at how to deal with this better. I ask for patience here.</p><p>Any number of elements can now be added or removed from this list. To save the changes, the StorageManager is informed of this. Let&rsquo;s look at this in a little more detail. We will expand and modify the previous source code for this. Instead of simple strings, a separate class with two attributes is now used. The first attribute is again a string and represents the data value. The second attribute is an instance of the type LocaDateTime and is an automatically set timestamp. We can already see here that it is no longer a simple attribute. The constructor takes over the data value, which can be modified later. The timestamp cannot be set explicitly. There is only one getter here. Such constructs can also be processed by EclipseStore without any further action.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">DataElement</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">value</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">LocalDateTime</span><span class="w"/><span class="n">timestamp</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">DataElement</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">value</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">value</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">timestamp</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">LocalDateTime</span><span class="p">.</span><span class="na">now</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setValue</span><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">value</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">value</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">value</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">timestamp</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">LocalDateTime</span><span class="p">.</span><span class="na">now</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">getValue</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">value</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">LocalDateTime</span><span class="w"/><span class="nf">getTimestamp</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">timestamp</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="nd">@Override</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">toString</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="s">"DataElement{"</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">"value='"</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">value</span><span class="w"/><span class="o">+</span><span class="w"/><span class="sc">'\''</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="s">", timestamp="</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">timestamp</span><span class="w"/><span class="o">+</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="sc">'}'</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h4 id="case-i---we-add-elements-to-the-container">Case I - We add elements to the container:</h4><p>In our case, the simplest case is to add one or more elements to the root element, a list of type DataElement. This is done by adding the desired elements to the list and calling the “storeRoot()” method at the root level or saving the elements yourself.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">EmbeddedStorageManager storageManager = EmbeddedStorage.start();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> storageManager.setRoot(new ArrayList<span class="nt">&lt;DataElement&gt;</span>());</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> storageManager.storeRoot();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> List<span class="nt">&lt;DataElement&gt;</span> root = (List<span class="nt">&lt;DataElement&gt;</span>) storageManager.root();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> root.add(new DataElement("Nr 1"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> root.add(new DataElement("Nr 2"));</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> storageManager.storeRoot();</span></span></code></pre></div></div><p>And here is the method in which the elements are specified directly.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">DataElement</span><span class="w"/><span class="n">d3</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DataElement</span><span class="p">(</span><span class="s">"Nr 3"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">DataElement</span><span class="w"/><span class="n">d4</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">DataElement</span><span class="p">(</span><span class="s">"Nr 4"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">root</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">d3</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">root</span><span class="p">.</span><span class="na">add</span><span class="p">(</span><span class="n">d4</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">storeAll</span><span class="p">(</span><span class="n">d3</span><span class="p">,</span><span class="w"/><span class="n">d4</span><span class="p">);</span></span></span></code></pre></div></div><h4 id="case-ii---we-remove-elements-from-the-container">Case II - We remove elements from the container:</h4><p>The opposite operation of adding elements is deleting them. Here, too, the process is very straightforward. However, looking at the different options is unusual at the beginning. First, it&rsquo;s the most intuitive version. Here, the element is removed from the holding object, the root itself. This reduced instance is then passed on to the StorageManager with the request to save this modification.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">root</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="n">d3</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">store</span><span class="p">(</span><span class="n">root</span><span class="p">);</span></span></span></code></pre></div></div><p>However, you can also use the removed element yourself. For this purpose, the element previously removed from the holding instance is passed to the StorageManager for storage. This approach works but is anything but intuitive. At this point, for reasons of comprehensibility, you should not write it. Your colleagues will be grateful for that.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">root</span><span class="p">.</span><span class="na">remove</span><span class="p">(</span><span class="n">d4</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">store</span><span class="p">(</span><span class="n">d4</span><span class="p">);</span></span></span></code></pre></div></div><h4 id="case-iii---we-modify-the-elements-within-the-container">Case III - We modify the elements within the container:</h4><p>Since elements can now be added and removed, the only thing missing is the modification. Here, too, the procedure is analogous to adding. The instance is modified and then passed to the StorageManager for storage. The saving process can, of course, also take place via a holding element. For readability reasons, however, saving the modified elements yourself is strongly advisable. Exception can, of course, be when you change a lot of elements within a holding instance.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">d3</span><span class="p">.</span><span class="na">setValue</span><span class="p">(</span><span class="s">"Nr 3. modified"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/></span></span><span class="line"><span class="cl"><span class="n">storageManager</span><span class="p">.</span><span class="na">store</span><span class="p">(</span><span class="n">d3</span><span class="p">);</span></span></span></code></pre></div></div><h3 id="fazit">Fazit:</h3><p>We have now seen how easy it is to get started with EclipseStore. Elements can now be saved without any further effort. This way, you can realize the first small applications. But that&rsquo;s not the end of it. The following parts will address the intricacies and challenges of more complex applications. So it remains exciting.</p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>EclipseStore</category><category>Java</category><media:content url="https://svenruppert.com/images/2023/10/DSC1536_landscape-crop.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2023/10/DSC1536_landscape-crop.jpeg"/><enclosure url="https://svenruppert.com/images/2023/10/DSC1536_landscape-crop.jpeg" type="image/jpeg" length="0"/></item><item><title>TDD and the impact on security</title><link>https://svenruppert.com/posts/tdd-and-the-impact-on-security/</link><pubDate>Wed, 28 Jun 2023 16:08:03 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/tdd-and-the-impact-on-security/</guid><description>Test-driven development (TDD) is a software development approach that prioritizes writing automated tests while creating the actual code. There follows a cycle of writing a failed test, writing the code to make the test pass, and then refactoring the code. TDD was originally developed to ensure the quality, maintainability and expandability of the software created over the long term. The specific knowledge about the individual source text passages should also be shown in the tests. Thus, a transfer of responsibility between developers is supported. Better than any documentation, tests are always up-to-date regarding the function that has been implemented in the source code.</description><content:encoded>&lt;![CDATA[<p>Test-driven development (TDD) is a software development approach that prioritizes writing automated tests while creating the actual code. There follows a cycle of writing a failed test, writing the code to make the test pass, and then refactoring the code. TDD was originally developed to ensure the quality, maintainability and expandability of the software created over the long term. The specific knowledge about the individual source text passages should also be shown in the tests. Thus, a transfer of responsibility between developers is supported. Better than any documentation, tests are always up-to-date regarding the function that has been implemented in the source code.</p><p>However, TDD also has a positive impact on the security of a program. And that&rsquo;s what we&rsquo;re going to look at now.</p><p>But first, let&rsquo;s get back to TDD. There are a variety of theoretical approaches here. I will briefly touch on them here to give an overview. The question to be addressed is whether the TDD approach influences the effect.</p><p><a href="https://youtu.be/S-GKA6KJwPc">https://youtu.be/S-GKA6KJwPc</a></p><h1 id="classic-tdd---kent-beck">Classic TDD - Kent Beck</h1><p>Unit tests and units tested with them are continuously developed in parallel. The actual programming is done in small, repeating micro-iterations. One such iteration, which should only take a few minutes, consists of three main parts, known as the red-green refactoring.</p><p>Red: Write a test to test a new behaviour (functionality) to be programmed. You start with the simplest example. If the feature is older, it could also be a known bug or new functionality to be implemented. This test is initially not fulfilled by the existing program code, so it must fail.</p><p>Green: Change the program code with as little effort as possible and add to it until it passes all tests after the test run then initiated.</p><p>Then clean up the code (refactoring): remove repetitions (code duplication), abstract if necessary, align with the binding code conventions, etc. In this phase, no new behaviour may be introduced that would not already be covered by tests. After each change, the tests are performed; if they fail, it is impossible to replicate what appears to be a bug in the code being used. Cleanup aims to make the code simple and easy to understand.</p><p>These three steps are repeated until the known bugs are fixed, the code provides the desired functionality, and the developer can&rsquo;t think of any more useful tests that could fail. The program-technical unit (unit) treated in this way is considered complete for now. However, the tests created together with her are retained to be able to test again after future changes as to whether the behavioural aspects that have already been achieved are still fulfilled.</p><p>For the changes in step 2 - also called transformations - to lead to the goal, each change must lead to a more general solution; for example, she may not only edit the current test case at the expense of others. Tests that get more and more detailed bring the code to a more and more general solution. Regular attention to transformation priorities leads to more efficient algorithms.</p><p>Consistently following this approach is an evolutionary design method, where every change evolves the system.</p><h1 id="outside-in-tdd">Outside in TDD</h1><p>Outside-In Test-Driven Development (TDD) is an approach to software development that places emphasis on starting the development process first by creating high-level acceptance tests or end-to-end tests that demonstrate the desired behaviour of the system from his point of view to define users or external interfaces. It is also commonly referred to as behaviour-directed development (BDD).</p><p>With Outside-In TDD, the development process begins with writing a failed acceptance test that describes the desired behaviour of the system. This test is usually written from a user&rsquo;s perspective or a high-level component that interacts with the system. The test is expected to initially fail as the system does not have the required functionality.</p><p>Once the first acceptance test has been performed, the next step is to write a failing unit test for the smallest possible unit of code that will pass the acceptance test. This unit test defines the desired behaviour of a specific module or component within the system. The unit test fails because the corresponding code still needs to be implemented.</p><p>The next step is implementing the code to make the failed unit test succeed. The code is written incrementally, focusing on the immediate needs of the failed test. The developer writes further tests and code step by step until the acceptance test is passed and the desired behaviour of the system is achieved.</p><p>The idea behind Outside-In TDD is to drive the development process from the outside, starting with the higher-level behaviour and moving inward to the lower-level implementation details. This approach helps ensure that the system is developed to meet the needs and expectations of its users. It also encourages component testability and decoupling by encouraging the creation of small, focused tests and modular code.</p><p>By practising outside-in TDD, developers can gain confidence in their code and ensure the system behaves as expected. It also helps uncover design flaws early in the development process and encourages the creation of loosely coupled and easily maintainable code.</p><p>Overall, Outside-In TDD is a methodology that combines testing and development, focusing on providing value to users and driving the development process from an outside perspective.</p><h1 id="acceptance-driven-tdd">Acceptance Driven TDD</h1><p>In test-driven development, system tests are continuously developed or at least specified before the system. In test-driven development, system development is no longer to meet written requirements but to pass specified system tests.</p><p>Acceptance Test-Driven Development (ATDD), while related to Test-Driven Development, differs in approach from Test-Driven Development. Acceptance test-driven development is a communication tool between the customer or user, the developer and the tester to ensure the requirements are well described. Acceptance test-driven development does not require automation of test cases, although it would be useful for regression testing. The acceptance tests for test-driven development must also be legible for non-developers. In many cases, the tests of test-driven development can be derived from the tests of acceptance test-driven development. For this approach to be used for system security, the boundary conditions must go beyond the normal technical aspects, which are only considered in some cases.</p><h1 id="what-test-coverage-should-i-use">What test coverage should I use?</h1><p>It is important for the effect on security that tests run automatically. These then lead to test coverage that can be measured and compared to previous runs. The question arises as to which test coverage will be most suitable. It has been shown that there are significant differences in the strength of the respective approaches. I am a strong proponent of mutation test coverage, as it is one of the most effective test coverages. If you want to know more here, I refer to my video on &ldquo;Mutation Testing in German&rdquo; or &ldquo;Mutation Testing in English&rdquo; on my YouTube channel. In any case, it is important that the better the test coverage, the greater the effect that can be achieved.</p><h1 id="how-exactly-is-the-security-of-the-application-supported">How exactly is the security of the application supported?</h1><p>1. Identify vulnerabilities early: Writing tests before implementing features help identify potential security gaps early. By considering security concerns during the test design phase, developers can anticipate possible attack vectors and design their code with security guidelines in mind. This proactive approach makes it possible to detect security problems before they become deeply embedded in the code base.</p><p>2. Encouraging Safe Coding Practices: TDD encourages writing modular, well-structured, and testable code. This enables developers to adopt secure coding practices such as input validation, proper error handling, and secure data storage. By writing tests that simulate different security scenarios, developers are more likely to consider edge cases and verify that their code behaves securely.</p><p>3. Regression Testing for Security: TDD relies on running automated tests regularly to maintain existing functionality as new code is added. This approach helps prevent a decline in security-related issues. If a security-related test case exists and initially passes, subsequent changes to it can be tested to ensure that vulnerabilities were not accidentally introduced.</p><p>4. Building a Security-Aware Culture: TDD&rsquo;s incorporation of security testing as an integral part of the development process helps foster a security-aware culture within the development team. By performing security-focused testing alongside functional testing, developers are more likely to prioritize security and see it as a fundamental aspect of their job. This mindset encourages a proactive attitude towards security rather than just seeing it as a side issue.</p><p>5. Facilitate Collaboration and Code Review: TDD encourages collaboration and code review between team members. When developers first write tests, they can discuss security concerns and get feedback from peers. This collaborative approach increases overall security awareness within the team and enables early detection and resolution of security issues.</p><p>Although TDD is not a security panacea, it offers a systematic and disciplined development approach that indirectly strengthens software security posture. By promoting security-centric thinking, promoting secure coding practices, and facilitating early detection of vulnerabilities, TDD can help build more secure and resilient applications. However, it is important to note that additional security measures such as threat modelling, code analysis, and penetration testing should also be included to meet security requirements fully.</p><h1 id="a-few-more-practical-approaches">A few more practical approaches</h1><h2 id="what-are-compliance-issues-and-how-to-remove-them">What are Compliance Issues, and how to remove them?</h2><p>The compliance issues are elements that run under a license and may not be used in this way in the project. This can happen in all technology areas within a project. Libraries or frameworks used do not usually change their license over the years, but it occasionally happens. All dependencies of the elements used must also be checked. And that&rsquo;s up to the last link.</p><p>One must replace the affected element with a semantic equivalent available under an appropriate license to fix such a violation.</p><h2 id="what-are-vulnerabilities-and-how-to-remove-them">What are Vulnerabilities, and how to remove them?</h2><p>Vulnerabilities are unintentional errors in the source code that allow the system to be manipulated to its advantage. These errors can occur throughout the tech stack. It is important to recognize all vulnerabilities in all technologies used to get an overview of which vulnerabilities can be exploited in which combination for an attack on this system. Individual considerations need to be clarified at this point. This holistic view enables the recognition of the different attack vectors and the identification of weak points that cannot be exploited in this system. This allows you to use your efforts in a targeted manner to secure the system.</p><h2 id="why-do-we-need-efficient-dependency-management">Why do we need efficient dependency management?</h2><p>What tools are available to developers to address compliance issues and vulnerabilities? The answer is obvious. This is where very effective dependency management combined with robust test coverage comes in handy. Because, in all cases, dependencies have to be exchanged. Even though it&rsquo;s a surrogate for compliance issues, fixing vulnerabilities is the same dependency in a different version. This can be a higher or lower version. Overall, it is about the clever combination of the various elements. Strong test coverage facilitates the subsequent test to determine whether the functionality is still available.</p><h1 id="conclusion">Conclusion:</h1><p>In summary, strong test coverage is beneficial when modifying and exchanging external elements as well as your own source code. Here you can rely on the results of the CI environment without having to add manual verifications and thus implement a fast and lean release process with as little time loss as possible.</p><p>Quality and safety support each other.</p><p>Happy coding</p><p>Sven</p>
]]></content:encoded><category>Security</category><category>TDD</category></item><item><title>Introduction to the Linux Foundation's SLSA project</title><link>https://svenruppert.com/posts/introduction-to-the-linux-foundations-slsa-project/</link><pubDate>Sat, 10 Dec 2022 21:56:43 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/introduction-to-the-linux-foundations-slsa-project/</guid><description>Supply Chain Security is a hot topic these days. And more and more, we as developers are dealing with this daily. But what does this mean for us, and how is this influencing our job? I want to give an overview of common attacks against the Software Supply Chain from the developer&rsquo;s view and will introduce the Open Source project SLSA from the Linux Foundation.</description><content:encoded>&lt;![CDATA[<p>Supply Chain Security is a hot topic these days. And more and more, we as developers are dealing with this daily. But what does this mean for us, and how is this influencing our job? I want to give an overview of common attacks against the Software Supply Chain from the developer&rsquo;s view and will introduce the Open Source project SLSA from the Linux Foundation.</p><p><strong>a) Who is the project SLSA</strong></p><p>Various experts from the security field started this project to share their knowledge, leading to the mentioned project. There is no company or governmental organisation behind this project. It is a pure Open Source project under the umbrella of the Linux Foundation.</p><p><strong>b) What is the goal of this project?</strong></p><p>The SLSA project is open-source and will not provide its own source code. It is, therefore, not a classic open-source project to publish a specific solution. Instead, it is a documentation project to process knowledge about supply chain security in software development and make it freely accessible. The structure contains examples of where the events or attacks mentioned have been successfully taken and which countermeasures should be taken. We&rsquo;ll look at the security levels provided a little later. The aim here is to allow the reader to prepare for these threats with specific steps slowly.</p><p>After all, based on your options and the current environment, you have to decide which next steps and measures make sense to implement.</p><p><a href="https://youtu.be/r8moPP8EACc">https://youtu.be/r8moPP8EACc</a></p><p><strong>c) What is the current status of the project?</strong></p><p>The project is currently (beginning of 2022) still in the alpha phase. There is a lot of documentation, but some places still provide references to future content. So it&rsquo;s always worth taking a look inside. Since this is an open-source project, you can, of course, also contribute yourself. Here you have the opportunity to make your expertise available to others.</p><p><strong>SLSA security levels?</strong></p><p><strong>a) Why do we need these security levels?</strong></p><p>When you look around the SLSA project website for the first time, the description of the security levels quickly catches your eye. But what are these levels all about? With these levels, you want to achieve two things. First, it should help to enable a self-assessment. This first consideration serves to classify the own existing environment and to identify the primary weak points. The second goal of this level is to help you plan the following purposes that should be implemented to increase your protection significantly. These recommendations are based on evaluating many projects and environments and on the security experts&rsquo; knowledge. This enables you to use the available means and opportunities to increase your security.</p><p><strong>b) Level 0</strong></p><p>The first level mentioned is marked with the number zero. This is the state in which no action has yet been taken. The documentation that describes the entire build process must be available and up-to-date to reach this level. This enables an initial analysis of the environment to be secured. It can also be seen as a way of taking stock.</p><p><strong>c) Level 1</strong></p><p>The level with the number one requires first actions on the environment. The point here is to list all the components used to create a binary file. Not only are the dependencies used listed, but all other metadata is recorded if possible. There is the term SBOM, Software Bills Of Materials in the industry. However, the term can be broadened considerably here. Within JFrog Artifactory, there is &ldquo;build info&rdquo;, which means a superset of SBOM. All sorts of metadata are recorded here, such as the operating system and version of the build machine, environment variables and their contents, etc. I have created my video for this.</p><p>In this video, I&rsquo;ll go over the details of SBOM and Build Info.</p><p><a href="https://www.youtube.com/watch?v=ILgZexU07dc">https://www.youtube.com/watch?v=ILgZexU07dc</a></p><p>Within the SLSA project, the term SBOM or Build Info is not explicitly mentioned. Instead, the requirement is described more generally, and it is also clearly pointed out that this level does not yet help against the compromise itself. It is only the basis for the follow-up and the comprehensive risk assessment. Based on this level, one can start with vulnerability management.</p><p><strong>d) Level 2</strong></p><p>Level two describes code versioning software in combination with a hosted build service. With this combination, you want to trust the build service and thus trust the origin of the generated files. Here you must take a critical look at where and who will offer these services. The fundamental consideration is based on the assumption that a specialized provider has a better grip on the issue of security within the build infrastructure than you can guarantee yourself. However, the manipulation of protection of the build service can be increased in various ways.</p><p><strong>e) Level 3</strong></p><p>With level three, auditors are now included in the considerations. Here, trained security specialists are used to check the existing build environment for possible vulnerabilities. As part of an accreditation process, the auditors determine whether specific standards are met to ensure the verifiability of the source and the integrity of the provenance (origin). This level offers better protection than all previous levels to ward off well-known threats. Preventing cross-build contamination is an example.</p><p><strong>f) Level 4</strong></p><p>In the SLSA project, level four is the highest currently achieved. To reach this level, the following conditions are set. The first requirement describes the necessity that two people constantly carry out all changes. This does not only refer to the changes made to the source code. The term is broader here and also includes all changes to the system and the configurations. This ensures that no intentional or unintentional compromise can occur at any point. Such a process has prevented most bugs, vulnerabilities and compromise attempts.</p><p>The second requirement relates to the technical environment. It is assumed here that it is a hermetic and reproducible creation process. However, a distinction should be made between the requirement for hermetic builds and reproducible builds. The former guarantees that all required dependencies are known and checked for vulnerabilities. Here, of course, it plays a role from which sources these dependencies were obtained. On the other hand, reproducible builds help detect changes and conduct subsequent analyses. However, the reproducibility of a build is not a mandatory part of the fourth level.</p><p><strong>g) Limitations</strong></p><p>Even if many supply chain threads are addressed with the SLSA project, there are some limitations that I would like to list here.</p><p><strong>1)</strong> The list of all dependencies in a project can simply become too large to be able to fully record and evaluate them. That can depend on different things. The point that shows this most clearly is based on the possibility of some dependency management systems not only specifying explicit dependency versions. In some scenarios, you can also define a version range. This now results in an exponential increase in all possible version combinations.</p><p><strong>2)</strong> The number of all components to be backed up can exceed the available capacities. The teams then have to decide which elements are subjected to safety analysis.</p><p><strong>3)</strong> The SLSA security levels are not transitive. This means that this component itself consists of components that have a lower security level. So here, you have to pay very close attention to the details.</p><p><strong>4)</strong> Even if automation is the solution in many areas, there may be areas where this will not be possible. Again, this refers in many areas to closed-source projects. Here, some analyzes are simply not feasible. It must then be decided how this dependency will be classified on a case-by-case basis.</p><p>**<em>What are the requirements?</em> **</p><p>All the requirements mentioned here are a little more detailed in reality. An overview of the requirements was provided to guide what exactly is meant by each point. Here, you can see which condition belongs to which level and how this is understood. Then the details are discussed, such as what difference it makes if you only use a building service or if you also use the &ldquo;Build as Code&rdquo; strategy. All the details are too extensive here, so I refer to the section on the project&rsquo;s website.</p><p><strong>Supply Chain Threads</strong></p><p>**a) Overview **</p><p>To better understand the project and its limitations, you should look at the individual types of attacks on a typical software development environment. A distinction is generally made between source, image and dependency threads. The SLSA project also delimits precisely what it includes and what it does not.</p><p>The following overview comes directly from the project and will be further developed if necessary. At this point, one must not forget that this project is only in the alpha phase (early 2022). However, these types of attacks mentioned are timeless and apply to almost every development environment.</p><p>Let&rsquo;s get to the source threads. This type of attack aims to compromise the source code itself.</p><p><strong>b.1) Bypass Code Review</strong></p><p>The attack called &ldquo;Bypass Code Review&rdquo; describes a scenario in which an attempt is made to bypass the control mechanisms and directly add compromised source code to the project. Here public paths are taken to reach the destination. Such attacks were carried out on the Linux kernel, for example, via the mailing list. The best and probably simplest way to thwart such attacks is to have all source code changes checked independently by at least two people. However, at this point, one must also find ways to prevent mutual &ldquo;covering up&rdquo;.</p><p><strong>b.2) Compromised Source Control System</strong></p><p>If importing compromised source code does not work, you can try to attack the CVS itself. Attempts are being made here to make changes to the source code on the server, bypassing the official channels. A prominent example is a successful attack on the source code of PHP. The self-managed system was attacked, and the changes were imported directly as a commit. Only securing the system helps against this type of attack. To manage such a system yourself, you must have the necessary resources and knowledge. In this case, it is often easier and cheaper to use a managed system with Github.</p><p>Next, we come to the build threads.</p><p><strong>b.3) Modified Source Code after Source Control</strong></p><p>The source code cannot only be changed where it is stored. When a Build process is started, the source code is fetched from the specified source. The source text on the build page can also be modified before the translation process starts. One attack in which this approach was used is the &ldquo;Webmin&rdquo; project. Here, additional and modified source texts were injected into the translation process. The agents responsible for carrying out the individual build steps must be secured to prevent this attack. Here, too, one has to weigh up how much of this work is carried out by oneself since how safe such an environment depends primarily on one&rsquo;s own abilities.</p><p><strong>b.4) Compromised Build Platform</strong></p><p>Since the built environment is part of the value chain, this part must also be secured classically. Here, too, it is again the case that one&rsquo;s own abilities in ​​safety must be assessed. A well-known representative of this type of attack is the Solarwinds Hack. Here, the build route was modified to compromise the newly created binary with each run.</p><p><strong>b.5) ​​Using Bad Dependencies - Dependency Thread</strong></p><p>At this point, I would like to insert the dependency threads briefly. Dependencies are specifically modified and then offered. The security of the repositories plays a central role here. I&rsquo;ll come back to that in a moment. However, this attack pattern refers to external locations where dependencies are fetched to process them. Particular attention should be paid to assessing the integrity of caches and mirrors of known official bodies. Often the &ldquo;official&rdquo; mirror servers of publicly accessible repositories do not have the same financial resources as the original. This causes attacks to be placed there.</p><p>You may be offered modified versions of known commonly used dependencies when you dock at such a place.</p><p><strong>b.6) Bypassed CI/CD</strong></p><p>Let&rsquo;s get back to the build threads. Sometimes it is easier to bypass the CI/CD environment at an appropriate point. Here, the topics within a development path are selected where a transfer between the build server and the associated repositories is not sufficiently secured. The attack involves offering a recipient such as a repository a modified binary to make it look like it is from the CI environment. Malicious code is injected parallel to its own build infrastructure.</p><p><strong>b.7) Compromised package repo</strong></p><p>A component repository is also part of the infrastructure and can fall victim to an attack. As with all other elements, protection is necessary here. Attackers try to gain access to the repositories to replace the binaries of known and frequently used dependencies with modified versions. Depending on your ability to harden such a system against attacks, you have to decide whether an externally managed alternative will increase the security standard of your value chain.</p><p><strong>b.8) Using a bad package</strong></p><p>We come to the last attack variant mentioned here. In this scenario, known dependencies are taken as a template and then their own modified versions are offered in a public repository. Here, an attempt is made to give the own variant a name close to the original name and depicts typical spelling errors. This dependency can still be resolved if a misspelling of the original name is used in a version definition. Only in this case a modified version of it will be loaded.</p><p>The only way to protect yourself against this attack is to carefully check how each individual dependency has been defined in your own projects.</p><p><strong>Project Persia Overview</strong></p><p>We have seen which general types of attacks on a classic software development environment are possible. These are always generic attack patterns that can be used independently of the current products. Others also saw this as an idea to develop a general strategy to counter this. The language here is from the open-source project Persia, which is currently (early 2022) in the alpha phase. We are close to officially accepting it as an incubator in the CD Foundation. The project website is<a href="https://pyrsia.io">https://pyrsia.io</a>.</p><p>The basic idea of ​​this project is that there will be a network of trust for open-source projects. A P2P network consisting of build and distribution nodes should exist where the sources are fetched, built and offered decentrally. In addition to the classic advantages such as reliability and better utilisation of the network resources of a P2P network, increased security should also be achieved with the binaries themselves. The types of attacks on source code management software are excluded. But all subsequent attack patterns can be repelled in this way.</p><p>Unfortunately, it is not possible to fully describe the project. But at this point, I would like to draw attention to my video about this project. (<a href="https://pyrsia.io/">https://pyrsia.io/</a><strong>)</strong></p><p><a href="https://youtu.be/M3QQww1JzlU">https://youtu.be/M3QQww1JzlU</a></p><p>Cheers Sven</p>
]]></content:encoded><category>DevSecOps</category><category>Security</category><category>Uncategorized</category><media:content url="https://svenruppert.com/images/2022/12/Folie2.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2022/12/Folie2.jpeg"/><enclosure url="https://svenruppert.com/images/2022/12/Folie2.jpeg" type="image/jpeg" length="0"/></item><item><title>The Power of #JFrog Build Info (Build Metadata)</title><link>https://svenruppert.com/posts/the-power-of-jfrog-build-info-build-metadata/</link><pubDate>Fri, 08 Oct 2021 13:42:05 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/the-power-of-jfrog-build-info-build-metadata/</guid><description>Intro
This article will take a detailed look at what the term build-info is all about and why it will help us protect against attacks such as the Solarwinds Hack.</description><content:encoded>&lt;![CDATA[<p><strong>Intro</strong></p><p>This article will take a detailed look at what the term build-info is all about and why it will help us protect against attacks such as the Solarwinds Hack.</p><p><a href="https://anchor.fm/svenruppert/episodes/The-Power-of-JFrog-Build-Info-Build-Metadata-e18gbfq"><figure><img src="/images/spotify-badge.svg" alt="" loading="lazy" decoding="async"/></a></p><p><strong>What is the concept behind the term - build-info?</strong></p><p>Let&rsquo;s start at the very beginning and clarify the basic principle behind the term build-info. The term build-info has been coined for many years by the company JFrog, among others. This is a particular type of repository.</p><p>This repository stores the information that describes the context that led to the creation of a binary file. With this information, you can now achieve a wide variety of things.</p><p><a href="https://youtu.be/ILgZexU07dc">https://youtu.be/ILgZexU07dc</a></p><p><strong>What components does build-info consist of?</strong></p><p>The content of a build-info is not strictly defined. Instead, the approach that applies is that the more, the better. Of course, you have to proceed with caution here too. All possible parameters are collected. In addition to the date and time, the system on which the process was run, which operating system was used in which patch level, to active environment variables, compiler switches and library versions.</p><p>The challenge is actually that it is not known which information will later be helpful and expedient. For this reason, more rather than less should be saved.</p><p><strong>Why do we actually need a build-info?</strong></p><p>The task of a build-info is to enable the observation, or rather, the analysis of a past situation. There can be a variety of reasons for this. For example, it can be used to improve quality, or it can be the basis for reconstructing a cyber attack that has taken place. And with that, we come straight to the event that got everything rolling in the recent past.</p><p><strong>Trigger - SolarWinds Hack</strong></p><p>One of the others will have heard or read something about it. We are talking about one of the most significant cyberattacks that have ever taken place. It&rsquo;s the SolarWinds Hack. Here it was not the final target that was attacked directly, but a point in the supply chain. SolarWinds is a software company that provides a product for managing network infrastructure. With just over 300,000 customers worldwide, this software&rsquo;s automatic update process has been the target of the attack. It was not the update process itself that was compromised, but the creation of the binaries that will be distributed with this update process. The attack took place on the company&rsquo;s CI route to immediately infect the newly created binaries with each build. Here the CI route was manipulated so that another component was added to the binary to be generated. This component can be thought of as a kind of initial charge. As soon as this has been ignited or activated, further components are dynamically reloaded. As a result, each infection had different forms. These files were then offered to all customers by means of an automatic update. Thus, over 15,000 systems were infiltrated within a short time.</p><p><strong>Reaction - Executive Order of Cybersecurity</strong></p><p>Since there were many well-known US companies, US organizations and US government institutions among the victims, the question arose of how to counter such a threat from the US state in the future. The US government has decided that one begins with the complete cataloguing of all software components in use, including all their constituent parts. This obligation to provide evidence was formulated in the &ldquo;Executive Order of Cybersecurity&rdquo;. However, when I first heard about an executive order, I wasn&rsquo;t sure what that actually meant.</p><p><strong>What is an executive order?</strong></p><p>An executive order is a decree of the US President that regulates or changes internal affairs within the state apparatus. You can think of it as a US president like a managing director of a company who can influence his company&rsquo;s internal processes and procedures. In doing so, no applicable law can be circumvented or changed. With such an executive order, no law can be changed, stopped or restricted. But it can change the internal processes of the US authorities very drastically. And that&rsquo;s exactly what happened with this Executive Order of Cybersecurity, which has directly impacted the US economy. Every company that works directly or indirectly for the state must meet these requirements to continue doing business with the US authorities.</p><p><strong>What is the key message of the Executive Order?</strong></p><p>The Executive Order of Cybersecurity contains a little more text, the content of which I would like to shorten here and reproduce without guaranteeing legal correctness.</p><p>This arrangement aims to record the software operated by or for the US authorities in its entirety. This means that all components of the software used must be documented. This can be thought of as follows; If you want to bake a cake, you need a list of ingredients. However, listing these would not meet the requirements, as knowledge of all elements down to the last link is required. In our example, an ingredient that consists of more than one component would have to provide a list of all existing parts. This is how you get all the components used that are needed for the cake. If we now transfer this to the software, it is necessary to record all direct and indirect dependencies. The list of all components is then called - SBOM (Software Bills Of Material)</p><p><strong>And what does that mean for me?</strong></p><p>Now I am not directly working for an organ of the US government. But it will still reach me sooner or later. Because through the indirection, all possible economic sectors worldwide will be affected. It can also affect me in the private sector. If I have an open-source project that is used indirectly in this environment, it can only continue if I prepare the project accordingly. Long story short; It will come our way in whatever way it will happen.</p><p><strong>What are the general requirements for a build-info?</strong></p><p>Let&rsquo;s get back to the build-info. A build-info is a superset of an SBOM (Software Bills Of Material). So the dependencies are catalogued, and additional information from the runtime environment in which the binary is generated is recorded. However, there are also some requirements for such build-info. By this, I mean properties that must be present in order to be able to deal with this information in a meaningful way.</p><p><strong>Accessibility:</strong></p><p>The information that is collected must be quickly and easily accessible and therefore usable. A central storage location such as Artifactory is suitable here, as all binaries and dependencies are also stored here. You also have all the meta-information of a respective binary file at this location.</p><p><strong>Immutability:</strong></p><p>When information is collected, it makes sense to store it in such a way that it can no longer be changed. This promotes the acceptance of the data situation and gives particular security when evaluating a case.</p><p><strong>Combinability:</strong></p><p>The static data are also suitable for enriching them with current secondary data. In this case, I am talking about the vulnerabilities that can be found in the dependencies used. In contrast to the static information, this image has to be constantly updated. In practical use, this means that the data must be stored in a machine-readable manner in order to enable the connection of other data sources. In the case of Artifactory, it is the data from JFrog Xray that is displayed here.</p><p><strong>How can a build-info be generated?</strong></p><p>It would be best if you had the following things.</p><p>First, the JFrog CLI generates the build-info and second, it is possible to save this information permanently. You can do this very quickly with the Build-Info Repositories from Artifactory. But one step at a time.</p><p>The process for generating a build-info is as follows.</p><p>In the following example, we assume that we are going to build a Java project with maven. Here you have to get involved in the build process. You can do that quite easily with the JFrog CLI tools. These represent a wrapper for the build process used in each case. In our example, it is maven. To install the JFrog CLI Tools, you can go on the website<a href="https://jfrog.com/getcli/">https://jfrog.com/getcli/</a> choose the installer that is suitable for it. After installing and configuring the JFrog CLI Tools, you can start the build via the CLI on the command line. After a successful build, you can find the build-info as a JSON file in the target directory. This file can be viewed with a text editor to get a feel for the wealth of information that is stored here. This file is regenerated with each build. This means that there is a 1: 1 relationship between the build cycle and the binaries generated in the process. Using the CLI, you can then transfer this information to the Artifactory build repository and evaluate it there using a graphical user interface.</p><p><strong>How can I link vulnerability scans to the build-info?</strong></p><p>If you are now within Artifactory (<a href="https://bit.ly/SvenYouTube">https://bit.ly/SvenYouTube</a> ), look at the build-information; you also get the current information regarding the known vulnerabilities. This is an example of how the build-information can be enriched with further system information.</p><p>Since there is not enough space at this point to clarify the generation and evaluation, I refer to a demo project in which I have stored the steps in the README, including detailed explanations of the individual practical steps.</p><p>If you want, you can also register for one of my free DevSecOps workshops. You are also welcome to speak to me directly if you&rsquo;re going to do this in a free community or company event.</p><p>The URL for the demo project is:</p><p><a href="https://github.com/Java-Workshops/JFrog-FreeTier-JVM">https://github.com/Java-Workshops/JFrog-FreeTier-JVM</a></p><p>Have fun trying it out.</p><p>Cheers, Sven!</p>
]]></content:encoded><category>DevSecOps</category><category>Security</category><media:content url="https://svenruppert.com/images/2021/10/Folie3.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2021/10/Folie3.jpeg"/><enclosure url="https://svenruppert.com/images/2021/10/Folie3.jpeg" type="image/jpeg" length="0"/></item><item><title>SolarWinds hack and the Executive Order from Mr Biden -- And now?</title><link>https://svenruppert.com/posts/solarwinds-hack-and-the-executive-order-from-mr-biden-and-now/</link><pubDate>Tue, 27 Jul 2021 11:10:15 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/solarwinds-hack-and-the-executive-order-from-mr-biden-and-now/</guid><description> In the past two years, we have had to learn a lot about cybersecurity. The new attack vectors are becoming more and more sophisticated and are directed more and more against the value chain in general. But what does that mean for us? What can be done about it, and what reactions have the state already taken?</description><content:encoded>&lt;![CDATA[<p><a href="https://open.spotify.com/show/0rZHMLs9fWq1G0Q2DAQbc3"><figure><img src="/images/spotify-badge.svg" alt="" loading="lazy" decoding="async"/></a></p><p>In the past two years, we have had to learn a lot about cybersecurity. The new attack vectors are becoming more and more sophisticated and are directed more and more against the value chain in general. But what does that mean for us? What can be done about it, and what reactions have the state already taken?</p><p>Let&rsquo;s start with the story that got all of this rolling and made sure that the general attention was drawn to the vulnerabilities of the available IT infrastructure. We&rsquo;re talking about the SolarWinds Hack. What happened here, and what is more critical; What are the lessons learned from this incident?</p><p><a href="https://youtu.be/ClYhATBlBl0">https://youtu.be/ClYhATBlBl0</a></p><p>It is essential to know that the SolarWinds company produces software that is used to manage network infrastructure. With the name &ldquo;Orion Platform, " this software should help manage and administer the network components efficiently. If you look at the product description on the SolarWinds website, you can read that the software can monitor, analyse, and manage the network infrastructure. And that&rsquo;s exactly what the customers did. The company itself has around 300,000 customers worldwide, including individual companies and government organisations and corporations from a wide variety of areas. To always stay up to date, the software platform includes an automatic update mechanism. And that was exactly what the attackers were after. They saw in the SolarWinds company a multiplier for their own activities. But how can you go about this? The attackers obtained the necessary software tools by breaking into the FireEye company and infiltrating the SolarWinds network. The goal was the software development department in which the binaries of the Orion platform are created. Here, the CI routes have been manipulated to include compromised binaries in every build of the software. As a result, the company produced these binaries and put them into circulation through the automatic update process. In this way, around 18,000 targets could be infiltrated and compromised within a short time.</p><p><strong>What does that mean for us now?</strong></p><p>There are two angles here. The first is from the consumer&rsquo;s point of view. However, for your own production, it must be ensured that no compromised binaries are used. That means that all, and here I mean all binaries used, must be checked. These are the dependencies of the application and the tools used in the production process. This includes the tools such as the CI server used, e.g. Jenkins, or the operating system components of the infrastructure and Docker images.</p><p>The second perspective represents the point of view that one has as a software distributor. This means anyone who distributes binaries in any form. This includes customers in the classic sense and other departments that may be considered consumers of the binaries created. The associated loss of trust can cause a company severe financial damage.</p><p><strong>What can we do to protect ourselves?</strong></p><p>If you look at the different approaches to arm yourself against these threats, you can see two fundamental differences.</p><p><em>DAST - Dynamic Application Security Testing</em></p><p>An application can be subjected to a black box test. This is called DAST, Dynamic Application Security Testing. Here you go the way a hacker would do it. The application is attacked via the available interaction points. Attack vectors based on the most common vulnerabilities are usually used here. SQL injection can be cited as an example. These attack patterns are initially independent of the technology used and represent typical security holes in web applications.</p><p>This approach has advantages as well as disadvantages. A serious disadvantage is that the tests cannot be started until the application has been developed. This means that the measures to eliminate weak points found are more costly than is the case with other approaches. On the other hand, a significant advantage of this approach is that the dynamic context can also be checked when testing the production system. Examples include weak configurations, open ports, and more.</p><p><em>SAST - Static Application Security Testing</em></p><p>The counterpart to the dynamic tests is the static tests. These tests analyse the individual components of the infrastructure involved. This includes not only the applications created but also the components involved in operating and creating the application.</p><p>A disadvantage of the SAST method is that it can only search for known security vulnerabilities. However, this point is put into perspective in that the number of known security gaps increases steadily. Still, it is also assumed that the number of known security gaps exceeds the number of unknown security gaps. Another advantage of the SAST method is that it checks the licenses used. This area also contributes to the security of the company, albeit in a legal sense.</p><p><strong>So what has the most significant effect when you start with the topic of security?</strong></p><p>When looking at the different approaches, it becomes clear that the proportion of directly or indirectly used binaries makes up the most significant amount in a technology stack. This means that you should also start securing precisely these parts. Unfortunately, there is only one way to ensure with SAST that it is really 100% checked directly. All other methods do not achieve this coverage, or if they do, then only in sporadic exceptional cases.</p><p>However, it should be noted that one understands the interdependencies. This is the only way to identify and extract the possible attack vector. Here it is of essential importance that not only the binaries themselves are examined, but also the associated meta-information is evaluated. Within software development, there are two points for these analyses where such an analysis fits very well. The first point that most people will think of is the CI segment. Here the CI server can carry out the analysis during the execution of a build pipeline and all the checks and also take over the reporting. If the violations are too massive, the build is cancelled. The second position, which is ideally suited for such an analysis, is directly in the IDE. Here it can already be determined during the definition whether the license used is permitted. The known vulnerabilities can also be extracted and thus provide enough information to decide whether this version of this dependency may be used.</p><p><strong>Lesson Learned - what we learned from the SolarWinds Hack</strong>.</p><p>We learned the following things from SolarWinds. First, the attacks are no longer aimed at the final targets themselves but against the suppliers. Here one tries to reach multipliers who massively increase the effectiveness of this attack. Likewise, you no longer only have to protect yourself against your use of compromised binaries. The newly added component means that the self-generated binaries must also be secured. Nobody wants to become a distributor of infected software. It follows that all parts involved must be subjected to permanent checks.</p><p><strong>Reactions - The Executive Order from Mr Biden</strong></p><p>One or the other has undoubtedly noticed that the US President gave this Executive Order of Cybersecurity. But what exactly does that mean, and who can even do something like that under what conditions?</p><p>In the United States, the president - the &ldquo;executive branch&rdquo; of the US government - can issue what is known as an executive order. Much like a CEO in a company, the executive branch has the power to make some unilateral decisions that affect the behaviour of the US government. These orders must be limited to government conduct and policies and not to general laws or statutes that encroach on the rights of any individual or state. If the executive transgresses in this area, the US Supreme Court or other legal remedies can appeal and revoke all measures. Only the president - due to his status as head of state - can issue an executive order. No other officer is allowed to do this.</p><p>Significantly, executive orders are mostly limited to how the US government operates internally. For example, an executive order might not simply resolve to raise taxes in a state or issue far-reaching commercial guidelines. However, executive orders are legally binding on the behaviour of the US government if they do not violate fundamental rights or make something unconstitutional. Because of this, these commands can be found to change the policy from the president to the president as, like a CEO, they set some of the government&rsquo;s agendas. In this case, the Executive Ordinance refers to how the US government monitors and regulates its software usage and sets out requirements for how that software must be documented to be sold to the government. It doesn&rsquo;t dictate trading on a broad scale.</p><p>Since the US government can define its way of working, it was decided that the SBOM requirements (in preparation) will be the only way to approve software purchases - to get an idea of ​​the volume, you have to know it is one amount in the tens of billions per year. This means that any company that wants to sell to US government agencies, likely state and local agencies, and anyone who works with resellers who sell to the government and more, must meet all of these new standards.</p><p>When the European Union agreed on comprehensive data protection and data security laws with the GDPR, the effect was felt worldwide and quickly. In a globally networked economy, decisions by the world&rsquo;s largest GDP producers can set standards for other areas. With the GDPR, companies in the US and elsewhere had to immediately revise and update software and processes to continue doing business in the EU. It became a worldwide rule so as not to lose EU treaties. Likewise, the US cybersecurity order is believed to be reversing, with U.S.-based global corporations influencing global software security behaviour.</p><p><strong>Wow - what now?</strong></p><p>What does that mean for me as a software developer? Well, the effects at this point are not as severe as you might want to imagine. In the end, it is said that a complete list of all contained dependencies must be created. This means the direct and indirect dependencies. To obtain such a dependency graph, one can, for example, use the tools that already do this. It means the tools that can generate a complete dependency graph, for example. For this, you need a logical point through which all dependencies are fetched. A tool such as Artifactory is best, as this is where the knowledge of all dependency managers used comes together. Devices such as the vulnerability scanner JFrog Xray can then access this information, scan for known vulnerabilities and licenses used, and extract the entire dependency graph. This information can thus be generated as build information by the CI route (e.g. JFrog Pipelines) and stored in a build repository within Artifactory.</p><p><strong>Conclusion</strong></p><p>We have seen that the new qualities of attacks on software systems, in general, will lead to advanced countermeasures. What is meant here is a complete analysis of all artefacts involved and produced. The tools required for this must combine the information into aggregated impact graphs across technology boundaries. One point within software development that is ideal for this is the logical point via which all binaries are fed into the system. However, the components alone are insufficient since all indirect elements can only be reached and checked by understanding the associated meta-information.</p><p>The tools used for this can then also be used to create the SBOMs.</p><p>In my opinion, it is only a matter of time before we also have to meet this requirement if we do not want to suffer economic disadvantages from the Executive Order of Cybersecurity.</p><p>If you want to find out more about the topic or test the tools briefly mentioned here, don&rsquo;t hesitate to contact me.</p><p>In addition to further information, we at JFrog offer free workshops to prepare for these challenges in a purely practical manner.</p><p>And if you want to know more immediately, you are welcome to go to my YouTube channel. There I have other videos on this topic.</p><p>It would be nice to leave a subscription on my YouTube channel as a small thank you.<a href="https://www.youtube.com/channel/UCNkQKejDX-pQM9-lKZRpEwA">https://www.youtube.com/channel/UCNkQKejDX-pQM9-lKZRpEwA</a></p><p>Cheers Sven</p>
]]></content:encoded><category>DevSecOps</category><category>Security</category><media:content url="https://svenruppert.com/images/2021/07/Talk-DevSecOps-Low-hanging-Fruits-202107_EN.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2021/07/Talk-DevSecOps-Low-hanging-Fruits-202107_EN.jpg"/><enclosure url="https://svenruppert.com/images/2021/07/Talk-DevSecOps-Low-hanging-Fruits-202107_EN.jpg" type="image/jpeg" length="0"/></item><item><title>What is the difference between SAST, DAST, IAST and RASP?</title><link>https://svenruppert.com/posts/what-is-the-difference-between-sast-dast-iast-and-rasp/</link><pubDate>Mon, 19 Jul 2021 15:34:30 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/what-is-the-difference-between-sast-dast-iast-and-rasp/</guid><description> Intro:
In this post, we&rsquo;re going to look at the differences between the various cybersecurity defence techniques. Here you can identify four main groups, which we will go through briefly one after another to illustrate the advantages and disadvantages.</description><content:encoded>&lt;![CDATA[<p><a href="https://open.spotify.com/show/0rZHMLs9fWq1G0Q2DAQbc3"><figure><img src="/images/spotify-badge.svg" alt="" loading="lazy" decoding="async"/></a></p><p><strong>Intro:</strong></p><p>In this post, we&rsquo;re going to look at the differences between the various cybersecurity defence techniques. Here you can identify four main groups, which we will go through briefly one after another to illustrate the advantages and disadvantages.</p><p><a href="https://youtu.be/sW7mTNVIUhE">https://youtu.be/sW7mTNVIUhE</a></p><p><strong>SAST - Static Application Security Testing</strong></p><p>SAST describes the process in which the components of an application are subjected to a static analysis. This approach not only searches for security gaps but also determines the licenses for the individual elements. In the following, however, I will only deal with the consideration of vulnerabilities in this post.</p><p>The term static comes from the fact that only the static context is used for this evaluation. All dynamic or context-related assessments are not possible with this method. SAST can be used to analyze the entire tech stack of an application if access to these components is possible, which is also one of the advantages of SAST compared to other analysis methods. The only exception is the IAST procedure, which we will come back to later.</p><p>What exactly happens now with the SAST procedure? SAST is available in two forms. The first that I would like to mention relates to checking your source code. Here the attempt is made to determine whether there are patterns that make a later vulnerability easier or even possible in the first place. For example, it searches for calls to critical libraries or identifies unsafe handling of resources. However, one honestly has to admit at this point that, first of all, your source code will be by far the smallest part of the overall application in the vast majority of projects. Second, the analysis methods in this area are still at a very early stage of development.</p><p>When using SAST, it makes a lot more sense to deal with all the other components first. Therefore, all binary files of the entire application environment are meant here. This also includes all elements that play a role in the development, such as the CI server and the other tools used.</p><p><strong>DAST - Dynamic Application Security Testing</strong></p><p>The DAST analysis method does precisely the opposite in comparison to the SAST method. Here the application is viewed as a black box. As soon as an executable version of the application is available, it is attacked through automatically executed cyber attacks. In most cases, the general attack patterns are based on the Most Common Vulnerabilities defined by the OSWAP. Defining your attack vectors is either not provided for in the tools I know or requires very detailed knowledge in this area. For developers who do not use these tools daily and intensively, their use is limited to the predefined attack vectors. It is a good thing with this procedure that unknown security gaps can also be identified, as long as they are based on the Most Common Vulnerabilities. Some manufacturers extend this approach with AI or ML techniques. This approach using AI and ML techniques is still in a very early stage of development, and the currently available potential cannot be foreseen.</p><p>Unfortunately, the method can only be used sensibly against the production environment, as this is the only way to identify all gaps based on configuration deficiencies. Therefore, in contrast to the SAST process, it makes no sense to use it within the CI route.</p><p><strong>IAST - Interactive Application Security Testing</strong></p><p>IAST uses software tools to evaluate application performance and identify vulnerabilities. IAST takes an &ldquo;agent-like&rdquo; approach; H. Agents and sensors run to continuously analyze application functions during automated tests, manual tests, or a mixture of both.</p><p>The process and feedback occur in real-time in the IDE, Continuous Integration (CI) environment or quality assurance or during production. The sensors have access to:</p><ul><li>All source code</li><li>Data and control flow</li><li>System configuration data</li><li>Web components</li><li>Backend connection data</li></ul><p>The main difference between IAST, SAST, and DAST is that it runs inside the application.</p><p>Access to all static components as well as the runtime information enables a very comprehensive picture.</p><p>It is a combination of static and dynamic analysis. However, the part of the dynamic analysis is not a pure black-box test as it is implemented at DAST.</p><p><strong>Some reasons for using IAST:</strong></p><p>Potential problems are identified earlier, so IAST minimizes the cost of eliminating potential costs and delays.</p><p>This is due to a &ldquo;shift left&rdquo; approach, meaning it is carried out in the early stages of the project life cycle.</p><p>Similar to SAST, the IAST analysis provides complete lines of data-rich code so that security teams can immediately lookout for a specific bug.</p><p>With the wealth of information that the tool has access to, the source of vulnerabilities can be precisely identified.</p><p>Unlike other dynamic software tests, IAST can be easily integrated into Continuous Integration and Deployment (CI / CD) pipelines.</p><p>The evaluations take place in real-time in the production environment.</p><p><strong>On the other hand:</strong></p><p>IAST tools can slow down the operation of the application.</p><p>This is based on the fact that the agents change the bytecode themselves. This leads to a lower performance of the overall system.</p><p>The change itself can, of course, also lead to problems in the production environment.</p><p>The use of agents naturally also represents a potential source of danger since these agents can also be compromised.</p><p>- See Solarwinds Hack</p><p><strong>RASP - Runtime Application Self Protection</strong></p><p>RASP is the approach to secure the application from within.</p><p>The backup takes place at runtime and generally consists of looking for suspicious commands when they are executed.</p><p><strong>What does that mean now?</strong></p><p>With the RASP approach, you can examine the entire application context on the production machine and real-time.</p><p>Here all commands that are processed are examined for possible attack patterns.</p><p>Therefore, this procedure aims to identify existing security gaps and attack patterns and those that are not yet known.</p><p>Here it goes very clearly into the use of AI and ML techniques.</p><p>RASP tools can usually be used in two operating modes.</p><p>The first operating mode (monitoring) is limited to observing and reporting possible attacks.</p><p>The second operating mode (protection) then includes implementing defensive measures in real-time and directly on the production environment.</p><p>RASP aims to fill the gap left by application security testing and network perimeter controls.</p><p>The two approaches (SAST and DAST) do not have sufficient visibility into real-time data and event flows to prevent vulnerabilities from sliding through the verification process or block new threats that were overlooked during development.</p><p>RASP is similar to Interactive Application Security Testing (IAST); the main difference is that IAST focuses on identifying vulnerabilities in the applications, and RASPs focus on protecting against cybersecurity attacks that can exploit these vulnerabilities or other attack vectors.</p><p><strong>The RASP technology has the following advantages:</strong></p><p>First, RASP complements SAST and DAST with an additional layer of protection after the application is started (usually in production).</p><p>It can be easily applied with faster development cycles.</p><p>Unexpected entries are checked and checked.</p><p>It enables you to react quickly to an attack by providing comprehensive analysis and information about the possible vulnerabilities.</p><p><strong>However, RASP tools have certain disadvantages:</strong></p><p>By sitting on the application server, RASP tools can adversely affect application performance.</p><p>In addition, the technology (RASP) may not be compliant with regulations or internal guidelines,</p><p>which allows the installation of other software or</p><p>the automatic blocking of services is permitted.</p><p>The use of this technology can also lead to the people involved believing themselves to be false security.</p><p>The application must also be switched offline until the vulnerability is eliminated.</p><p>RASP is not a substitute for application security testing because it cannot provide comprehensive protection.</p><p>While RASP and IAST have similar methods and uses, RASP does not perform extensive scans but instead runs as part of the application to examine traffic and activity. Both report attacks as soon as they occur; with IAST, this happens at the time of the test, while with RASP, it takes place at runtime in production.</p><p><strong>Conclusion</strong></p><p>All approaches result in a wide range of options for arming yourself against known and unknown security gaps. Here it is essential to reconcile your own needs and those of the company for the future direction.</p><p><strong>Conclusion RASP:</strong></p><p>We have seen that with RASP, there is an approach in which the application can protect itself against attacks at runtime. The permanent monitoring of your activities and the data transferred to the application enable an analysis based on the runtime environment. Here you can choose between pure monitoring or alerting and active self-protection. However, software components are added to the runtime environment with RASP approaches to manipulate the system independently. This has an impact on performance. With this approach, RASP concentrates on the detection and defence of current cyber attacks. So it analyzes the data and user behaviour in order to identify suspicious activities.</p><p><strong>Conclusion IAST:</strong></p><p>The IAST approach combines the SAST and DAST approach and is already used within the SDLC, i.e. within the development itself. This means that the IAST tools are already further &ldquo;to the left&rdquo; compared to the RASP tools. Another difference to the RASP tools is that IAST consists of static, dynamic and manual tests. Here it also becomes clear that IAST is more in the development phase. The combination of dynamic, static and manual tests promises a comprehensive security solution. However, one should not underestimate the complexity of the manual and dynamic security tests at this point.</p><p><strong>Conclusion DAST:</strong></p><p>The DAST approach focuses on how a hacker would approach the system. The overall system is understood like a black box, and the attacks occur without knowing the technologies used. The point here is to harden the production system against the Most Common Vulnerabilities. However, one must not forget at this point that this technology can only be used at the end of the production cycle.</p><p><strong>Conclusion SAST:</strong></p><p>If you have access to all system components, the SAST approach can be used very effectively against known security gaps and license problems. This procedure is the only guarantee that the entire tech stack can be subjected to direct control. The focus of the SAST approach is on static semantics and, in turn, is completely blind to security holes in the dynamic context. A huge advantage is that this approach can be used with the first line of source code.</p><p><strong>My personal opinion:</strong></p><p>My personal opinion on these different approaches is as follows:</p><p>If you start with DevSecOps or security in IT in general, the SAST approach makes the most sense. This is where the greatest potential threat can be eliminated with minimal effort. It is also a process that can be used in all steps of the production line. Only when all components in the system are secured against known security gaps do the following methods show their highest potential. After introducing SAST, I would raise the IAST approach and, finally, the RASP approach. This also ensures that the respective teams can grow with the task and that there are no obstacles or delays in production.</p><p>Cheers Sven</p>
]]></content:encoded><category>DevSecOps</category><category>Security</category><media:content url="https://svenruppert.com/images/2021/07/Serie-DevSecOps-SAST_DAST_IAST_and_RASP.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2021/07/Serie-DevSecOps-SAST_DAST_IAST_and_RASP.jpg"/><enclosure url="https://svenruppert.com/images/2021/07/Serie-DevSecOps-SAST_DAST_IAST_and_RASP.jpg" type="image/jpeg" length="0"/></item><item><title>The Lifeline of a Vulnerability</title><link>https://svenruppert.com/posts/the-lifeline-of-a-vulnerability/</link><pubDate>Fri, 25 Jun 2021 16:17:29 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/the-lifeline-of-a-vulnerability/</guid><description> Intro Again and again, we read something in the IT news about security gaps that have been found. The more severe the classification of this loophole, the more attention this information will get in the general press. Most of the time, you don&rsquo;t even hear or read anything about all the security holes found that are not as well known as the SolarWinds Hack, for example. But what is the typical lifeline of such a security gap?</description><content:encoded>&lt;![CDATA[<p><a href="https://open.spotify.com/show/0rZHMLs9fWq1G0Q2DAQbc3"><figure><img src="/images/spotify-badge.svg" alt="" loading="lazy" decoding="async"/></a></p><h2 id="intro">Intro</h2><p>Again and again, we read something in the IT news about security gaps that have been found. The more severe the classification of this loophole, the more attention this information will get in the general press. Most of the time, you don&rsquo;t even hear or read anything about all the security holes found that are not as well known as the SolarWinds Hack, for example. But what is the typical lifeline of such a security gap?</p><p><a href="https://youtu.be/2_b0KxUl4vs">https://youtu.be/2_b0KxUl4vs</a></p><h2 id="vulnerability-was-generated-until-found">Vulnerability was generated until found</h2><p>Let&rsquo;s start with the birth of a vulnerability. This birth can be done in two differently motivated ways. On the one hand, it can happen to any developer that he creates a security hole by an unfortunate combination of source code pieces. On the other hand, it can also be based on targeted manipulation. However, this has essentially no effect on the further course of the lifeline of a security vulnerability. In the following, we assume that a security hole has been created and that it is now active in some software. These can be executable programs or libraries offered that are integrated into other software projects as a dependency.</p><h2 id="found-until-public-available">Found until Public available</h2><p>In most cases, it is not possible to understand precisely when a security hole was created. But let&rsquo;s assume that there is a security hole and that it will be found. It clearly depends on which person or which team finds this weak point. This has a severe impact on the subsequent course of history. Let&rsquo;s start with the fact that this vulnerability has been found by people who are interested in using this vulnerability themselves or having it exploited by other parties. Here the information is either kept under lock and key or offered for purchase at relevant places on the Internet. There are primarily financial or political motives here, which I do not want to go into here. However, at this point, it can be clearly seen that the information is passed on at this point in channels that are not generally available to the public.</p><p>However, if the security gap is found by people or groups who are interested in making the knowledge about it available to the general public, various mechanisms now take effect. However, one must not forget that commercial interests will also play a role in most cases. However, the motivation is different. If the company or the project itself is affected by this vulnerability, there is usually an interest in presenting the information relatively harmless. The feared damage can even lead to the security gap being fixed, but knowledge about it further is hidden. This approach is to be viewed as critical, as it must be assumed that there will also be other groups or people who will gain this knowledge.</p><p>But let&rsquo;s assume that people who are not directly involved in the affected components found the information about the vulnerability. In most cases, this is the motivation to sell knowledge of the vulnerability. In addition to the affected projects or products, there are also providers of the vulnerability databases. These companies have a direct and obvious interest in acquiring this knowledge. But to which company will the finder of the vulnerability sell this knowledge? Here it can be assumed that there is a very high probability that it will be the company that pays the better price. This has another side effect that affects the classification of the vulnerability. Many vulnerabilities are given an assessment using the CVSS. Here, the base value is made by different people. Different people will have other personal interests here, which will then also be reflected in this value.</p><p><strong><em>Here I refer to the blog post about CVSS - explained.</em></strong><a href="https://svenruppert.com/2021/04/07/cvss-explained-the-basics/">https://svenruppert.com/2021/04/07/cvss-explained-the-basics/</a></p><p>Regardless of the detours via which the knowledge comes to the vulnerabilities databases. Only when the information has reached one of these points can one assume that this knowledge will be available to the general public over time.</p><h2 id="public-available-until-consumable">Public available until consumable</h2><p>However, one fact can be seen very clearly at this point. Regardless of which provider of vulnerabilities you choose, there will only ever be a subset of all known vulnerabilities in this data set. As an end consumer, there is only one sensible way to go here. Instead of contacting the providers directly, you should rely on integrators. This refers to services that integrate various sources themselves and then offer them processed and merged. It is also essential that the findings are processed so that further processing by machines is possible. This means that the meta-information such as the CVE or the CVSS value is supplied. This is the only way other programs can work with this information. The CVSS value is given as an example. This is used, for example, in CI environments to interrupt further processing when a particular threshold value is reached. Only when the information is prepared in this way and is available to the end-user can this information be consumable. Since the information generally represents a considerable financial value, it can be assumed in the vast majority of cases that the commercial providers of such data sets will have access to updated information more quickly than freely available data collections.</p><h2 id="consumable-until-running-in-production">Consumable until running in Production</h2><p>If the information can now be consumed, i.e. processed with the tools used in software development, the storyline begins in your projects. Whichever provider you have decided on, this information is available from a particular point in time, and you can now react to it yourself. The requirement is that the necessary changes are activated in production as quickly as possible. This is the only way to avoid the potential damage that can result from this security vulnerability. This results in various requirements for your software development processes. The most obvious need is the throughput times. Only those who have implemented a high degree of automation can enable short response times in the delivery processes. It is also an advantage if the team concerned can make the necessary decisions themselves and quickly. Lengthy approval processes are annoying at this point and can also cause extensive damage to the company.</p><p>Another point that can release high potential is the provision of safety-critical information in all production stages involved. The earlier the data is taken into account in production, the lower the cost of removing security gaps. We&rsquo;ll come back to that in more detail when the shift-left topic is discussed.</p><p>Another question that arises is that of the effective mechanisms against vulnerabilities.</p><h2 id="testcoverage-is-your-safety-belt-try-mutation-testing">TestCoverage is your safety-belt; try Mutation Testing</h2><p>The best knowledge of security gaps is of no use if this knowledge cannot be put to use. But what tools do you have in software development to take efficient action against known security gaps? Here I would like to highlight one metric in particular: the test coverage of your own source code parts. If you have strong test coverage, you can make changes to the system and rely on the test suite. If a smooth test of all affected system components has taken place, nothing stands in the way of making the software available from a technical point of view.</p><p>But let&rsquo;s take a step back and take a closer look at the situation. In most cases, changing the version of the same dependency in use will remove known vulnerabilities. This means that efficient version management gives you the agility you need to be able to react quickly. In very few cases, the affected components have to be replaced by semantic equivalents from other manufacturers. And to classify the new composition of versions of the same ingredients as valid, strong test coverage is required. Manual tests would go far beyond the time frame and cannot be carried out with the same quality in every run. But what is strong test coverage?</p><p>I use the technique of mutation testing. This gives you more concrete test coverage than is usually the case with the conventional line or branch coverage. Unfortunately, a complete description of this procedure is not possible at this point.</p><p><em>However, if you want to learn more about mutation testing, visit the following URL. This video explains the theoretical and practical part of the mutation test for Java and Kotlin.</em></p><p><a href="https://www.youtube.com/watch?v=6Vej7YEOF8g">https://www.youtube.com/watch?v=6Vej7YEOF8g</a></p><h2 id="the-need-for-a-single-point-that-understands-all-repo-types">The need for a single point that understands all repo-types</h2><p>If we now assume that we want to search for known security vulnerabilities in the development and the operation of the software, we need a place where we can carry out the search processes. Different areas are suitable here. However, there is a requirement that enables an efficient scan across all technologies used. We are talking about a logical central point through which all binaries must go. I don&rsquo;t just mean the jar files declared as dependencies, but also all other files such as the Debian packages or Docker images. Artifactory is suitable for this as a central hub as it supports pretty much all package managers at one point. Because you have knowledge of the individual files and have the metadata, the following things can also be evaluated.</p><p>First of all, it is not only possible to capture the direct dependencies. Knowing the structures of the package managers used means that all indirect dependencies are also known. Second, it is possible to receive cross-technology evaluations. This means the full impact graph to capture the practical meaning of the individual vulnerabilities. The tool from JFrog that can give an overview here is JFrog Xray, and we are directly connected to JFrog Artifactory. Whichever tool you choose, it is crucial that you don&rsquo;t just scan one technology layer. Only with a comprehensive look at the entire tech stack can one guarantee that there are no known security gaps, either directly or indirectly, in production.</p><h2 id="conclusion">Conclusion</h2><p>Now we come to a conclusion. We have seen that we have little influence on most sections of a typical lifeline of an IT security vulnerability. Actually, there are only two sections that we can influence directly. On the one hand, it is the quickest and most comprehensive possible access to reliable security databases. Here it is essential that you not only entrust yourself to one provider but rather concentrate on so-called “mergers” or “aggregators”. The use of such supersets can compensate for the economically motivated vagueness of the individual providers. I named JFrog Xray as an example of such an aggregator. The second section of a typical lifeline is in your own home. That means, as soon as you know about a security gap, you have to act yourself—robust automation and a well-coordinated DevSecOps team help here. We will deal with precisely this section from the “security” perspective in another blogpost. Here, however, we had already seen that strong test coverage is one of the critical elements in the fight against vulnerabilities. Here I would like to refer again to the test method “Mutation Testing”, which is a very effective tool in TDD.</p><p><strong>And what can I do right now?</strong></p><p>Aou can, of course, take a look at my YouTube channel and find out a little more about the topic there. I would be delighted to welcome you as my new subscriber. Thanks!</p><p><a href="https://www.youtube.com/@OutdoorNerd">https://www.youtube.com/@OutdoorNerd</a></p><p>Happy Coding</p><p>Sven</p>
]]></content:encoded><category>DevSecOps</category><category>Security</category><media:content url="https://svenruppert.com/images/2021/06/Serie-DevSecOps-Lebenslinie-einer-Vulnerability_202106_Thumbnails_01.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2021/06/Serie-DevSecOps-Lebenslinie-einer-Vulnerability_202106_Thumbnails_01.jpeg"/><enclosure url="https://svenruppert.com/images/2021/06/Serie-DevSecOps-Lebenslinie-einer-Vulnerability_202106_Thumbnails_01.jpeg" type="image/jpeg" length="0"/></item><item><title>CVSS - explained - the Basics</title><link>https://svenruppert.com/posts/cvss-explained-the-basics/</link><pubDate>Wed, 07 Apr 2021 12:20:21 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/cvss-explained-the-basics/</guid><description> Intro What is the Common Vulnerability Scoring System short called CVSS, who is behind it, what are we doing with it and what a CVSS Value means for you? I will explain how a CVSS Score is calculated, what the different elements of it mean and what are the differences between the different CVSS versions.</description><content:encoded>&lt;![CDATA[<p><a href="https://open.spotify.com/show/0rZHMLs9fWq1G0Q2DAQbc3"><figure><img src="/images/spotify-badge.svg" alt="" loading="lazy" decoding="async"/></a></p><h3 id="intro">Intro</h3><p>What is the Common Vulnerability Scoring System short called CVSS, who is behind it, what are we doing with it and what a CVSS Value means for you? I will explain how a CVSS Score is calculated, what the different elements of it mean and what are the differences between the different CVSS versions.</p><h3 id="the-basic-idea-of-cvss">The Basic Idea Of CVSS</h3><p>The basic idea behind CVSS is to provide a general classification of the severity of a security vulnerability. This is about the classification and evaluation of weak points. But what does the abbreviation CVSS mean?</p><p><em>What does the abbreviation CVSS mean?</em></p><p>The letters stand for the words: Common Vulnerability Scoring System. That means something like a general vulnerability rating system. Here, the weak points found are evaluated from various points of view. These elements are weighted against each other so that a standardized number between 0 and 10 is obtained at the end.</p><p><em>For what do you need such a rating system?</em></p><p>A rating system that provides us with a standardized number allows us to evaluate different weak points abstractly and derive follow-up actions from them. The focus here is on standardizing the handling of these weak points. As a result, you can define actions based on the value ranges. Here I mean processes in the value creation sources that are affected by this weak point.</p><p><em>What is the basic structure of this assessment?</em></p><p>In principle, CVSS can be described so that the probability and the maximum possible damage are related using predefined factors. The basic formula for this is:<strong>risk = probability of occurrence x damage</strong></p><p><a href="https://youtu.be/CZrS_UFT37Y">https://youtu.be/CZrS_UFT37Y</a></p><h3 id="the-basic-values-from-010">The Basic Values from 0..10</h3><p>The evaluation in the CVSS is based on various criteria and is called &ldquo;metrics&rdquo;. For each metric, one or more values selected from a firmly defined selection option. This selection then results in a value between 0.0 and 10.0. Where 0 is the lowest and 10 is the highest risk value. The entire range of values ​​is then subdivided into groups and are labelled &ldquo;<strong>None</strong> &ldquo;, &ldquo;<strong>Low</strong> &ldquo;, &ldquo;<strong>Medium</strong> &ldquo;, &ldquo;<strong>High</strong> &ldquo;, and &ldquo;<strong>Critical</strong> &ldquo;. These metrics are divided into three areas that are weighted differently from one another. These are the areas &ldquo;Basic Metrics&rdquo;, &ldquo;Temporal Metrics&rdquo;, and &ldquo;Environmental Metrics&rdquo;. Here, different aspects are queried in each area, which must be assigned a single value. The weighting among each other and the subsequent composition of the three group values ​​gives the final result.</p><p>However, all component values ​​that lead to this result are always supplied. This behaviour ensures that there is transparency at all times about how these values ​​originally came about. Next, the three sub-areas will be explained individually in detail.</p><h3 id="the-basic-metrics">The Basic Metrics</h3><p>The basic metrics form the foundation of this rating system. The aim is to record the technical details of the vulnerability that will not change over time. You can imagine it to be an assessment that is independent of other changing elements. Different parties can carry out the calculation of the base value. It can be done by the discoverer, the manufacturer of the project or product concerned or by a party (CERT) charged with eliminating this weak point. One can imagine that, based on this initial decision, the value itself will turn out differently since the individual groups pursue different goals.</p><p><em>necessary requirements:</em></p><p>The base value evaluates the prerequisites that are necessary for a successful attack via this security gap. This is, for example, the distinction between whether a user account must be available on the target system for an attack that is used in the course of the attack or whether the system can be compromised without the knowledge about a system user. It also plays a significant role in whether a system is vulnerable over the Internet or whether physical access to the affected component is required.</p><p><em>Complexity of the attack:</em></p><p>The base value should also reflect how complex the attack is to carry out. In this case, the complexity relates to the necessary technical steps and includes assessing whether the interaction with a regular user is essential. Is it sufficient to encourage any user to interact, or does this user have to belong to a specific system group (e.g. administrator)? At this point, it is already evident that the assessment of a new vulnerability requires exact knowledge of this vulnerability and the systems concerned. The correct classification is not a trivial process.</p><p><em>Assessment of the damage:</em></p><p>The basic metrics also take into account the damage that this attack could cause to the affected component. This means the possibilities to extract the data from the system, manipulate it, or completely prevent the system&rsquo;s use. One speaks here of the three areas;</p><ul><li>Confidentiality</li><li>Integrity</li><li>Availability</li></ul><p>However, you have to be careful here concerning the weighting of these possibilities. In one case, it can be worse when data is stolen than it is changed. In another case, the unusability of a component can be the worst damage to be assumed.</p><p><em>Scope-Metric:</em></p><p>The scope metric has also been available since CVSS version 3.0. This metric looks at the effects of an affected component on other system components. For example, one can imagine that a compromised element in a virtualized environment enables access to the carrier system. A successful change of this scope represents a greater risk for the overall system and is therefore also evaluated using this factor. This point alone clearly shows that the interpretation of the values also requires adjusting to one&rsquo;s situation. And so we come to the &ldquo;temporal&rdquo; and &ldquo;environment&rdquo; metrics.</p><h3 id="the-temporal-metrics">The Temporal Metrics</h3><p>The time-dependent components of the vulnerability assessment are brought together in the group of temporal metrics. The peculiarity at this point is that the base value can be reduced by the temporal components only. The initial rating is intended to represent the worst-case scenario.</p><p>This has both advantages and disadvantages if you bear in mind that it is during the initial assessment of a vulnerability that can give very different interests. At this point, there are two things that need to be highlighted;</p><p>_1) Which factors influence the temporal metrics? _</p><p>The elements that change over time influence the &ldquo;Temporal Metrics&rdquo;.</p><p>On the one hand, this refers to changes concerning the availability of tools that support the exploitation of the vulnerability. These can be exploits or step-by-step instructions. A distinction must be made whether a chess point is theoretical or whether a manufacturer has officially confirmed it. All of these events change the base value.</p><p><em>2) Influence of the initial evaluation?</em></p><p>The influence on the initial evaluation comes about through external framework conditions. These take place over an undefined time frame and are not relevant for the actual basic assessment. Even if an exploit is already in circulation during the base values survey, this knowledge will not be included in the primary assessment. However, the base value can only be reduced by the temporal metrics. This approach takes a little getting used to and is often the subject of criticism. The reason why you decided on it is understandable from the theoretical point of view. The base value is intended to denote the most excellent possible damage.</p><p>And this is where a conflict arises. The person or group who has found a security gap tries to set the base value as high as possible. A highly critical loophole is better to sell and better exploited in the media. The reputation of the person/group who found this gap increases as a result. The affected company or the affected project is interested in exactly the opposite assessment. It, therefore, depends on who finds the security gap, how the recycling should take place and by which body the first evaluation is carried out. The only offsetting component is the Environmental Metrics.</p><h3 id="the-environmental-metrics">The Environmental Metrics</h3><p>In the case of environmental metrics, the own system landscape is set in relation to the security gap. This means that the evaluation is adjusted based on the real situation. In contrast to Temporal Metrics, Environmental Metrics can correct the base value in both directions. The environment can therefore lead to a higher classification and must also be constantly adapted to your own changes. The combination now gives rise to purely practical questions. Let&rsquo;s assume that there is a patch from the manufacturer for a security hole. The mere presence of this modification leads to a lowering of the total value in the Temporal Metrics. However, as long as the patch has not been activated in your own systems, the overall value must be drastically corrected upwards again via the Environmental Metrics. Why did I say at this point that the value has to be increased drastically? As soon as a patch is available, this patch can be used to better understand the security gap and its effects. The attacker has more and more detailed information that can be used. This reduces the resistance of the not yet hardened systems.</p><h3 id="the-final-score">The Final Score</h3><p>At the end of an evaluation, the final score is obtained, calculated from the three previously mentioned values. At this point, I will not explain the calculation details; I have a separate post on this. The resulting value is then assigned to a value group. But there is one more point that is very important to me personally. In many cases, I see that the final score is simply carried over. The individual adjustments utilizing the environmental score do not take place. Instead, the value one to one is adopted as the risk assessment. In many cases, this behaviour leads to a dangerous evaluation which is incorrect for the overall system concerned.</p><h3 id="conclusion">Conclusion</h3><p>We come to the management summary. With the CVSS we have a value system for evaluating security gaps in software. Since there are no alternatives, the system has been in use worldwide for over ten years and is constantly being developed, it is a defacto standard. The evaluation consists of three components. First, there is the basic score, which is there to depict a purely technical worst-case scenario. The second component is the evaluation of the time-dependent corrections based on external influences. This is about the evaluation of whether there are further findings, tools or patches for this security gap. The peculiarity of this point is that the base score can only be reduced with this value. The third component is the assessment of your own system environment with regard to this weak point. With this consideration, the security gap is adjusted in relation to the real situation on site. This value can lower or higher the base rating. Last but not least, an overall evaluation is made from these three values, which results in a number between 0.0 to 10.0.</p><p>This final value can be used to control your own actions to defend against the security gap in the overall context. At first glance, everything is quite abstract. It takes some practice to get a feel for the figures it contains.</p><p>The CVSS can only develop its full effect if it deals more deeply with this matter within your own system.</p><p>I will explain the underlying calculations in detail in further posts.</p>
]]></content:encoded><category>DevSecOps</category><media:content url="https://svenruppert.com/images/2021/04/Serie-DevSecOps-CVSS-explained-202103_Thumbnail.jpeg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2021/04/Serie-DevSecOps-CVSS-explained-202103_Thumbnail.jpeg"/><enclosure url="https://svenruppert.com/images/2021/04/Serie-DevSecOps-CVSS-explained-202103_Thumbnail.jpeg" type="image/jpeg" length="0"/></item><item><title>Pattern from the practical life of a software developer</title><link>https://svenruppert.com/posts/pattern-from-the-practical-life-of-a-software-developer/</link><pubDate>Tue, 09 Mar 2021 21:28:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/pattern-from-the-practical-life-of-a-software-developer/</guid><description>Builder-Pattern The book from the &ldquo;gang of four&rdquo; is part of the essential reading in just about every computer science branch. The basic patterns are described and grouped to get a good start on the topic of design patterns. But how does it look later in use?
Here we will take a closer look at one pattern and expand it.</description><content:encoded>&lt;![CDATA[<h1 id="builder-pattern">Builder-Pattern</h1><p>The book from the &ldquo;gang of four&rdquo; is part of the essential reading in just about every computer science branch. The basic patterns are described and grouped to get a good start on the topic of design patterns. But how does it look later in use?<br>
Here we will take a closer look at one pattern and expand it.</p><hr><p><a href="https://youtu.be/n7X_XT55-jw">https://youtu.be/n7X_XT55-jw</a></p><hr><h2 id="the-pattern---builder">The Pattern - Builder</h2><p>The builder pattern is currently enjoying increasing popularity as it allows you to build a fluent API.<br>
It is also lovely that an IDE can generate this pattern quite quickly. But how about using this design pattern in daily life?</p><h3 id="the-basic-builder-pattern">The basic builder pattern</h3><p>Let&rsquo;s start with the basic pattern, the initial version with which we have already gained all our experience.<br>
For example, I&rsquo;ll take a<strong><em>Car</em></strong> class with the<strong><em>Engine</em></strong> and<strong><em>List</em></strong><strong>&lt; Wheels&gt;</strong> attributes. A car&rsquo;s description is certainly not very precise, but it is enough to demonstrate some specific builder-pattern behaviours.</p><p>Now let&rsquo;s start with the<strong>Car</strong> class.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Car</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">Engine</span><span class="w"/><span class="n">engine</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">List</span><span class="w"/><span class="n">wheelList</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//SNIPP</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>At this point, I leave out the get and set methods in this listing. If you generate a builder for this, you get something like the following.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public static final class Builder {</span></span><span class="line"><span class="cl"> private Engine engine;</span></span><span class="line"><span class="cl"> private List<span class="nt">&lt;Wheel&gt;</span> wheelList;</span></span><span class="line"><span class="cl"> private Builder() {</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public Builder withEngine(Engine engine) {</span></span><span class="line"><span class="cl"> this.engine = engine;</span></span><span class="line"><span class="cl"> return this;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public Builder withWheelList(List<span class="nt">&lt;Wheel&gt;</span> wheelList) {</span></span><span class="line"><span class="cl"> this.wheelList = wheelList;</span></span><span class="line"><span class="cl"> return this;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public Car build() {</span></span><span class="line"><span class="cl"> return new Car(this);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> }</span></span></code></pre></div></div><p>Here the builder is implemented as a static inner class. The constructor of the &ldquo;Car&rdquo; class has also been modified.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="nf">Car</span><span class="p">(</span><span class="n">Builder</span><span class="w"/><span class="n">builder</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">setEngine</span><span class="p">(</span><span class="n">builder</span><span class="p">.</span><span class="na">engine</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">wheelList</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">builder</span><span class="p">.</span><span class="na">wheelList</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>On the one hand, there has been a change from<strong>public</strong> to<strong>private</strong> , and on the other hand, an instance of the builder has been added as a method parameter.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="n">Car</span><span class="w"/><span class="n">car</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Car</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">withEngine</span><span class="p">(</span><span class="n">engine</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">withWheelList</span><span class="p">(</span><span class="n">wheels</span><span class="p">)</span></span></span></code></pre></div></div><h3 id="an-example---the-car">An example - the car</h3><p>If you now work with the Builder Pattern, you get to the point where you have to build complex objects. Let us now extend our example by looking at the remaining attributes of the<strong>Car</strong> class.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class Car {</span></span><span class="line"><span class="cl"> private Engine engine;</span></span><span class="line"><span class="cl"> private List<span class="nt">&lt;Wheel&gt;</span> wheelList;</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl">public class Engine {</span></span><span class="line"><span class="cl"> private int power;</span></span><span class="line"><span class="cl"> private int type;</span></span><span class="line"><span class="cl">}</span></span><span class="line"><span class="cl">public class Wheel {</span></span><span class="line"><span class="cl"> private int size;</span></span><span class="line"><span class="cl"> private int type;</span></span><span class="line"><span class="cl"> private int colour;</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Now you can have a corresponding builder generated for each of these classes. If you stick to the basic pattern, it looks something like this for the class<strong>Wheel</strong> :</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">Builder</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">size</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">type</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="kt">int</span><span class="w"/><span class="n">colour</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="nf">Builder</span><span class="p">()</span><span class="w"/><span class="p">{}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">Builder</span><span class="w"/><span class="nf">withSize</span><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">size</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">size</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">size</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">this</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">Builder</span><span class="w"/><span class="nf">withType</span><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">type</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">type</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">type</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">this</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">Builder</span><span class="w"/><span class="nf">withColour</span><span class="p">(</span><span class="kt">int</span><span class="w"/><span class="n">colour</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">colour</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">colour</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">this</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">Wheel</span><span class="w"/><span class="nf">build</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Wheel</span><span class="p">(</span><span class="k">this</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>But what does it look like if you want to create an instance of the class<strong>Car</strong>? For each complex attribute of<strong>Car</strong> , we will create an instance using the builder. The resulting source code is quite extensive; at first glance, there was no reduction in volume or complexity.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class Main {</span></span><span class="line"><span class="cl"> public static void main(String[] args) {</span></span><span class="line"><span class="cl"> Engine engine = Engine.newBuilder().withPower(100).withType(5).build();</span></span><span class="line"><span class="cl"> Wheel wheel1 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();</span></span><span class="line"><span class="cl"> Wheel wheel2 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();</span></span><span class="line"><span class="cl"> Wheel wheel3 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();</span></span><span class="line"><span class="cl"> List<span class="nt">&lt;Wheel&gt;</span> wheels = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> wheels.add(wheel1);</span></span><span class="line"><span class="cl"> wheels.add(wheel2);</span></span><span class="line"><span class="cl"> wheels.add(wheel3);</span></span><span class="line"><span class="cl"> Car car = Car.newBuilder()</span></span><span class="line"><span class="cl"> .withEngine(engine)</span></span><span class="line"><span class="cl"> .withWheelList(wheels)</span></span><span class="line"><span class="cl"> .build();</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> System.out.println("car = " + car);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>This source code is not very nice and by no means compact. So how can you adapt the builder pattern here so that on the one hand you have to write as little as possible by the builder himself and on the other hand you get more comfort when using it?</p><h2 id="wheellistbuilder">WheelListBuilder</h2><p>Let&rsquo;s take a little detour first. To be able to raise all potentials, we have to make the source text homogeneous. This strategy enables us to recognize patterns more easily. In our example, the creation of the<strong>List<Wheel/> is to be outsourced to a builder, a<strong>WheelListBuilder</strong>.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class WheelListBuilder {</span></span><span class="line"><span class="cl"> public static WheelListBuilder newBuilder(){</span></span><span class="line"><span class="cl"> return new WheelListBuilder();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> private WheelListBuilder() {}</span></span><span class="line"><span class="cl"> private List<span class="nt">&lt;Wheel&gt;</span> wheelList;</span></span><span class="line"><span class="cl"> public WheelListBuilder withNewList(){</span></span><span class="line"><span class="cl"> this.wheelList = new ArrayList<span class="err">&lt;</span>&gt;();</span></span><span class="line"><span class="cl"> return this;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public WheelListBuilder withList(List wheelList){</span></span><span class="line"><span class="cl"> this.wheelList = wheelList;</span></span><span class="line"><span class="cl"> return this;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public WheelListBuilder addWheel(Wheel wheel){</span></span><span class="line"><span class="cl"> this.wheelList.add(wheel);</span></span><span class="line"><span class="cl"> return this;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public List<span class="nt">&lt;Wheel&gt;</span> build(){</span></span><span class="line"><span class="cl"> //test if there are 4 instances....</span></span><span class="line"><span class="cl"> return this.wheelList;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Now our example from before looks like this:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public class Main {</span></span><span class="line"><span class="cl"> public static void main(String[] args) {</span></span><span class="line"><span class="cl"> Engine engine = Engine.newBuilder().withPower(100).withType(5).build();</span></span><span class="line"><span class="cl"> Wheel wheel1 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();</span></span><span class="line"><span class="cl"> Wheel wheel2 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();</span></span><span class="line"><span class="cl"> Wheel wheel3 = Wheel.newBuilder().withType(2).withColour(3).withSize(4).build();</span></span><span class="line"><span class="cl"> List<span class="nt">&lt;Wheel&gt;</span> wheelList = WheelListBuilder.newBuilder()</span></span><span class="line"><span class="cl"> .withNewList()</span></span><span class="line"><span class="cl"> .addWheel(wheel1)</span></span><span class="line"><span class="cl"> .addWheel(wheel2)</span></span><span class="line"><span class="cl"> .addWheel(wheel3)</span></span><span class="line"><span class="cl"> .build();//more robust if you add tests at build()</span></span><span class="line"><span class="cl"> Car car = Car.newBuilder()</span></span><span class="line"><span class="cl"> .withEngine(engine)</span></span><span class="line"><span class="cl"> .withWheelList(wheelList)</span></span><span class="line"><span class="cl"> .build();</span></span><span class="line"><span class="cl"> System.out.println("car = " + car);</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Next, we connect the<strong>Wheel</strong> class builder and the<strong>WheelListBuilder</strong> class. The goal is to get a fluent API so that we don&rsquo;t create the instances of the<strong>Wheel</strong> class individually and then use the<strong>addWheel(Wheel w)</strong> method to<strong>WheelListBuilder</strong> need to add. It should then look like this for the developer in use:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">List</span><span class="w"/><span class="n">wheels</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">wheelListBuilder</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addWheel</span><span class="p">().</span><span class="na">withType</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="na">withSize</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">withColour</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">addWheelToList</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addWheel</span><span class="p">().</span><span class="na">withType</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="na">withSize</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">withColour</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">addWheelToList</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addWheel</span><span class="p">().</span><span class="na">withType</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="na">withSize</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">withColour</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">addWheelToList</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addWheel</span><span class="p">().</span><span class="na">withType</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="na">withSize</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">withColour</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">addWheelToList</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">build</span><span class="p">();</span></span></span></code></pre></div></div><p>So what happens here is the following: As soon as the<strong>addWheel()</strong> method is called, a new instance of the class<strong>WheelBuilder</strong> should be returned. The<strong>addWheelToList()</strong> method creates the representative of the<strong>Wheel</strong> class and adds it to the list. To do that, you have to modify the two builders involved. The<strong>addWheelToList()</strong> method is added to the<strong>WheelBuilder</strong> side. This adds the instance of the<strong>Wheel</strong> class to the<strong>WheelListBuilder</strong> and returns the instance of the<strong>WheelListBuilder</strong> class.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">private</span><span class="w"/><span class="n">WheelListBuilder</span><span class="w"/><span class="n">wheelListBuilder</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="n">WheelListBuilder</span><span class="w"/><span class="nf">addWheelToList</span><span class="p">(){</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">wheelListBuilder</span><span class="p">.</span><span class="na">addWheel</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="na">build</span><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">wheelListBuilder</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>On the side of the<strong>WheelListBuilder</strong> class, only the method<strong>addWheel()</strong> is added.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">Wheel</span><span class="p">.</span><span class="na">Builder</span><span class="w"/><span class="nf">addWheel</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Wheel</span><span class="p">.</span><span class="na">Builder</span><span class="w"/><span class="n">builder</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Wheel</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">builder</span><span class="p">.</span><span class="na">withWheelListBuilder</span><span class="p">(</span><span class="k">this</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">builder</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>If we now transfer this to the other builders, we come to a pretty good result:</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="n">Car</span><span class="w"/><span class="n">car</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Car</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addEngine</span><span class="p">().</span><span class="na">withPower</span><span class="p">(</span><span class="n">100</span><span class="p">).</span><span class="na">withType</span><span class="p">(</span><span class="n">5</span><span class="p">).</span><span class="na">done</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addWheels</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addWheel</span><span class="p">().</span><span class="na">withType</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="na">withSize</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">withColour</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">addWheelToList</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addWheel</span><span class="p">().</span><span class="na">withType</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="na">withSize</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">withColour</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">addWheelToList</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addWheel</span><span class="p">().</span><span class="na">withType</span><span class="p">(</span><span class="n">1</span><span class="p">).</span><span class="na">withSize</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">withColour</span><span class="p">(</span><span class="n">2</span><span class="p">).</span><span class="na">addWheelToList</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">done</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">build</span><span class="p">();</span></span></span></code></pre></div></div><h2 id="the-nestedbuilder">The NestedBuilder</h2><p>So far, the builders have been modified individually by hand. However, this can be implemented generically quite easily since it is just a tree of builders.</p><p>Every builder knows his children and his father. The implementations required for this can be found in the<strong>NestedBuilder</strong> class. It is assumed here that the methods for setting attributes always begin with the prefix<strong>with</strong>. Since this seems to be the case with most generators for builders, no manual adjustment is necessary here. The method<strong>done()</strong> sets the result of his method<strong>build()</strong> on his father. The call is made using reflection. With this, a father knows the authority of the child. At this point, I assume that the name of the attribute is the same as the class name. We will see later how this can be achieved with different attribute names. The method<strong>withParentBuilder</strong>(..) enables the father to announce himself to his child. We have a bidirectional connection now.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">xml</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl">public abstract class NestedBuilder<span class="nt">&lt;T</span><span class="err">,</span><span class="err">V</span><span class="nt">&gt;</span> {</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public T done() {</span></span><span class="line"><span class="cl"> Class<span class="err">&lt;</span>?&gt; parentClass = parent.getClass();</span></span><span class="line"><span class="cl"> try {</span></span><span class="line"><span class="cl"> V build = this.build();</span></span><span class="line"><span class="cl"> String methodname = "with" + build.getClass().getSimpleName();</span></span><span class="line"><span class="cl"> Method method = parentClass.getDeclaredMethod(methodname, build.getClass());</span></span><span class="line"><span class="cl"> method.invoke(parent, build);</span></span><span class="line"><span class="cl"> } catch (NoSuchMethodException</span></span><span class="line"><span class="cl"> | IllegalAccessException</span></span><span class="line"><span class="cl"> | InvocationTargetException e) {</span></span><span class="line"><span class="cl"> e.printStackTrace();</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> return parent;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl"> public abstract V build();</span></span><span class="line"><span class="cl"> protected T parent;</span></span><span class="line"><span class="cl"/></span><span class="line"><span class="cl"> public<span class="nt">&lt;P</span><span class="err">extends</span><span class="err">NestedBuilder&lt;T,</span><span class="err">V</span><span class="nt">&gt;</span>&gt; P withParentBuilder(T parent) {</span></span><span class="line"><span class="cl"> this.parent = parent;</span></span><span class="line"><span class="cl"> return (P) this;</span></span><span class="line"><span class="cl"> }</span></span><span class="line"><span class="cl">}</span></span></code></pre></div></div><p>Now the specific methods for connecting with the children can be added to a father. There is no need to derive from<strong>NestedBuilder</strong>.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Parent</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">KidA</span><span class="w"/><span class="n">kidA</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">KidB</span><span class="w"/><span class="n">kidB</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//snipp.....</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">Builder</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">KidA</span><span class="w"/><span class="n">kidA</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">KidB</span><span class="w"/><span class="n">kidB</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//snipp.....</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// to add manually</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">KidA</span><span class="p">.</span><span class="na">Builder</span><span class="w"/><span class="n">builderKidA</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">KidA</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">().</span><span class="na">withParentBuilder</span><span class="p">(</span><span class="k">this</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">KidB</span><span class="p">.</span><span class="na">Builder</span><span class="w"/><span class="n">builderKidB</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">KidB</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">().</span><span class="na">withParentBuilder</span><span class="p">(</span><span class="k">this</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">KidA</span><span class="p">.</span><span class="na">Builder</span><span class="w"/><span class="nf">addKidA</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">return</span><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">builderKidA</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">KidB</span><span class="p">.</span><span class="na">Builder</span><span class="w"/><span class="nf">addKidB</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/><span class="k">return</span><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">builderKidB</span><span class="p">;</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//---------</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">Parent</span><span class="w"/><span class="nf">build</span><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Parent</span><span class="p">(</span><span class="k">this</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>And with the children, it looks like this: Here, you only have to derive from<strong>NestedBuilder</strong>.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">KidA</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">String</span><span class="w"/><span class="n">note</span><span class="p">;</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//snipp.....</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">Builder</span><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">NestedBuilder</span><span class="o">&lt;</span><span class="n">Parent</span><span class="p">.</span><span class="na">Builder</span><span class="p">,</span><span class="w"/><span class="n">KidA</span><span class="o">&gt;</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">//snipp.....</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>The use is then very compact, as shown in the previous example.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Main</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Parent</span><span class="w"/><span class="n">build</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Parent</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addKidA</span><span class="p">().</span><span class="na">withNote</span><span class="p">(</span><span class="s">"A"</span><span class="p">).</span><span class="na">done</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addKidB</span><span class="p">().</span><span class="na">withNote</span><span class="p">(</span><span class="s">"B"</span><span class="p">).</span><span class="na">done</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"build = "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">build</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Any combination is, of course, also possible. This means that a proxy can be a father and child at the same time. Nothing stands in the way of building complex structures.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">Main</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="p">(</span><span class="n">String</span><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">Parent</span><span class="w"/><span class="n">build</span><span class="w"/><span class="o">=</span><span class="w"/><span class="n">Parent</span><span class="p">.</span><span class="na">newBuilder</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addKidA</span><span class="p">().</span><span class="na">withNote</span><span class="p">(</span><span class="s">"A"</span><span class="p">)</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">addKidB</span><span class="p">().</span><span class="na">withNote</span><span class="p">(</span><span class="s">"B"</span><span class="p">).</span><span class="na">done</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">done</span><span class="p">()</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">.</span><span class="na">build</span><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">System</span><span class="p">.</span><span class="na">out</span><span class="p">.</span><span class="na">println</span><span class="p">(</span><span class="s">"build = "</span><span class="w"/><span class="o">+</span><span class="w"/><span class="n">build</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>Happy Coding</p>
]]></content:encoded><category>Java</category><media:content url="https://svenruppert.com/images/2021/03/CoreJava-NestedBuilder-Thumbnail.jpg" medium="image"/><media:thumbnail url="https://svenruppert.com/images/2021/03/CoreJava-NestedBuilder-Thumbnail.jpg"/><enclosure url="https://svenruppert.com/images/2021/03/CoreJava-NestedBuilder-Thumbnail.jpg" type="image/jpeg" length="0"/></item><item><title>Delegation Versus Inheritance In Graphical User Interfaces</title><link>https://svenruppert.com/posts/delegation-versus-inheritance-in-graphical-user-interfaces/</link><pubDate>Thu, 18 Feb 2021 17:26:16 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/delegation-versus-inheritance-in-graphical-user-interfaces/</guid><description> Intro
In this article, we will look at the difference between the inheritance and delegation concepts. Or, to put it better, why I prefer delegation and why I want to emphasize this rarely-used feature in Java.</description><content:encoded>&lt;![CDATA[<p><a href="https://open.spotify.com/show/0rZHMLs9fWq1G0Q2DAQbc3"><figure><img src="/images/spotify-badge.svg" alt="" loading="lazy" decoding="async"/></a></p><p><strong>Intro</strong></p><p>In this article, we will look at the difference between the inheritance and delegation concepts. Or, to put it better, why I prefer delegation and why I want to emphasize this rarely-used feature in Java.</p><p><strong>The Challenge</strong></p><p>The challenge we face today is quite common in the field of graphic user interfaces like desktop- or web-apps. Java is widely used as the development language for both worlds, and it does not matter if we are in the classic swing, JavaFX, or the field of web frameworks like Vaadin. Explicitly, I&rsquo;ve opted for a pseudo-class model in core Java, as I&rsquo;d like to look at the design patterns here without any technical details.</p><p>The goal is to create a custom component that consists of a text input field and a button. Both elements should be displayed next to each other, i.e. in a horizontal layout. The respective components have no function in this example. I want to be here exclusively to work towards the differences between inheritance and delegation.</p><p><strong>To lazy to read? Check-out my Youtube Version!</strong></p><p><a href="https://youtu.be/xgViS-wlH7Q">https://youtu.be/xgViS-wlH7Q</a></p><p><strong>The Base Class Model</strong></p><p>Mostly, there are the respective essential components in a framework. In our case, it is a TextField, a button, and a horizontal or vertical layout. However, all of these components are embedded in an inheritance structure. In our case, I chose the following construction. Each component corresponds to the Component interface, for which there is an abstract implementation called AbstractComponent.</p><p>The class AbstractComponent contains framework-specific and technologically-based implementations. The **Button, **as well as the<strong>TextField</strong> , extend the class<strong>AbstractComponent</strong>. Layouts are usually separate and, therefore, a specialized group of components that leads in our case to an abstract class named<strong>Layout</strong> , which inherits from the class<strong>AbstractComponent</strong>.</p><p>In this abstract class, there are layout-specific implementations that are the same for all sorts of layouts. The implementations<strong>HorizontalLayout</strong> and<strong>VerticalLayout</strong> based on this. Altogether, this is already a quite complex initial model.</p><figure><img src="/images/2021/02/sru_20210211-001.png" alt="" loading="lazy" decoding="async"/><p><strong>Inheritance — First Version</strong></p><p>In the first version, I show a solution that I have often seen in projects. As a basis for a custom component, a base component from the framework is used as a parent. The direct inheritance from a layout is often used to structure all other internally child components on the screen. Inside the constructor, the internally required elements are generated and added to the inherited layout structure.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">InputComponent</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">Horizontal</span><span class="w"/><span class="n">Layout</span><span class="w"/><span class="c1">// Layout is abstract</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">button</span><span class="w"/><span class="n">button</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="n">textField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">InputComponent</span><span class="w"/><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addComponent</span><span class="w"/><span class="p">(</span><span class="n">text</span><span class="w"/><span class="n">field</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addComponent</span><span class="w"/><span class="p">(</span><span class="n">button</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">click</span><span class="w"/><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">button</span><span class="p">.</span><span class="na">click</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setText</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">text</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">textField</span><span class="p">.</span><span class="na">setText</span><span class="w"/><span class="p">(</span><span class="n">text</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">getText</span><span class="w"/><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">textField</span><span class="p">.</span><span class="na">getText</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><p>If you now look at how the component will behave during later use, it becomes visible that a derivation from a fundamental component brings its pitfalls with it.</p><p>What exactly happened here? If an instance of the custom component<strong>InputComponent</strong> is now used, it can be viewed as a layout. But that is not the case here anymore; on the contrary, it is even wrong. All methods inherited from the layout implementation are also public available with this component. But you wanted to achieve something else. First of all, we wanted to reuse the existing code, provided in the component implementation<strong>HorizontalLayout</strong>.</p><p>On the other hand, you want a component that externally delegates only the methods for the necessary interaction, needed for the custom behaviour. In this case, the public methods from the Button and the<strong>TextField</strong> used symbolically. Besides, this component is tied to visual design that leads to possible interactions that are not part of the domain-specific behaviour of this component. This technical debt should be avoided as much as possible.</p><p>In practical words, general methods from the implementation of the<strong>HorizontalLayout</strong> are made visible to the outside. If somebody uses exactly these methods, and later on the parent becomes a<strong>VerticalLayout</strong> , the source code can not compile without further corrections.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainM01</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="w"/><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">inputComponent</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">InputComponent</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputComponent</span><span class="p">.</span><span class="na">setText</span><span class="w"/><span class="p">(</span><span class="s">"Hello Text M01"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputComponent</span><span class="p">.</span><span class="na">click</span><span class="w"/><span class="p">();</span><span class="w"/><span class="c1">// critical things</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputComponent</span><span class="p">.</span><span class="na">doSomethingLayoutSpecific</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputComponent</span><span class="p">.</span><span class="na">horizontalSpecific</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputComponent</span><span class="p">.</span><span class="na">doFrameworkSpecificThings</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><h2 id="inheritance--second-version">Inheritance — Second Version</h2><p>The custom component has to fit into the already existing component hierarchy from the framework. A place must be found inside the inheritance to start from; otherwise, the custom component cannot be used. But at the same time, we do not want to own specific implementation details, and neither the effort to implement basic technical requirements based on the framework needs. The point from which you split up the inheritance must be used wisely.</p><p>Please assume that the class<strong>AbstractComponent</strong> is what we are looking for as a start point.<br>
If you derive your class from it, so you certainly have the essential features that you would like to have as a user of the framework. However, this abstraction mostly associated with the fact that also framework-specific things are to be considered. This abstract class is an internally used, fundamental element. Starting with this internal abstract class very likely leads to the need to implement internal and technical related methods. As an example, the method signature with the name<strong>doFrameworkSpecificThings()</strong> has been created and implemented with just a log message.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">InputComponent</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">AbstractComponent</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">button</span><span class="w"/><span class="n">button</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="n">textField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">InputComponent</span><span class="w"/><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">layout</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">HorizontalLayout</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">layout</span><span class="p">.</span><span class="na">addComponent</span><span class="w"/><span class="p">(</span><span class="n">text</span><span class="w"/><span class="n">field</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">layout</span><span class="p">.</span><span class="na">addComponent</span><span class="w"/><span class="p">(</span><span class="n">button</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">addComponent</span><span class="w"/><span class="p">(</span><span class="n">layout</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">click</span><span class="w"/><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">button</span><span class="p">.</span><span class="na">click</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setText</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">text</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">textField</span><span class="p">.</span><span class="na">setText</span><span class="w"/><span class="p">(</span><span class="n">text</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">getText</span><span class="w"/><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">textField</span><span class="p">.</span><span class="na">getText</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// to deep into the framework for EndUser</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">doFrameworkSpecificThings</span><span class="w"/><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">logger</span><span class="w"/><span class="p">().</span><span class="w"/><span class="n">info</span><span class="w"/><span class="p">(</span><span class="s">"doFrameworkSpecificThings -"</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="o">+</span><span class="w"/><span class="k">this</span><span class="p">.</span><span class="na">getClass</span><span class="w"/><span class="p">().</span><span class="w"/><span class="n">getSimpleName</span><span class="w"/><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>In use, such a component is already a little less dangerous. Only the internal methods that are visible on other components are accessible on this component.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainM02</span><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="w"/><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">inputComponent</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">InputComponent</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputComponent</span><span class="p">.</span><span class="na">setText</span><span class="w"/><span class="p">(</span><span class="s">"Hello Text M02"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputComponent</span><span class="p">.</span><span class="na">click</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="c1">// critical things</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputComponent</span><span class="p">.</span><span class="na">doFrameworkSpecificThings</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>But I am not happy with this solution yet. Very often, there is no requirement for new components on the technical side. Instead, they are compositions of already existing essential elements, composed in a professional, domain-specific context.</p><p><strong>Composition — My Favorite</strong></p><p>So what can you do at this point? The beautiful thing about the solution is that you can use it to put a wrapper around already existing components, which have been generated by inheritance. One solution may be to create a composite of type<strong>T</strong>.** Composite<T extends= AbstractComponent=>**</p><p>This class serves as an envelope for the compositions of the required components. This class can then even itself inherit from the interface Component, so those technical methods of the abstract implementation not repeated or released to the outside. The type<strong>T</strong> itself is the type to be used as the external component that holds in the composition. In our case, it is the horizontal layout. With the method<strong>getComponent(</strong>), you can access this instance if necessary.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">final</span><span class="w"/><span class="kd">class</span><span class="nc">InputComponent</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">extends</span><span class="w"/><span class="n">Composite</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">implements</span><span class="w"/><span class="n">HasLogger</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">button</span><span class="w"/><span class="n">button</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">Button</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">private</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="n">textField</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">TextField</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="nf">InputComponent</span><span class="w"/><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">super</span><span class="w"/><span class="p">(</span><span class="k">new</span><span class="w"/><span class="n">Horizontal</span><span class="w"/><span class="nf">Layout</span><span class="w"/><span class="p">());</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">getComponent</span><span class="p">().</span><span class="na">addComponent</span><span class="p">(</span><span class="n">text</span><span class="w"/><span class="n">field</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">getComponent</span><span class="p">().</span><span class="na">addComponent</span><span class="w"/><span class="p">(</span><span class="n">button</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">click</span><span class="w"/><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/><span class="n">button</span><span class="p">.</span><span class="na">click</span><span class="w"/><span class="p">();</span><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">setText</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="w"/><span class="n">text</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">textField</span><span class="p">.</span><span class="na">setText</span><span class="w"/><span class="p">(</span><span class="n">text</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="n">String</span><span class="w"/><span class="nf">getText</span><span class="w"/><span class="p">()</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="k">return</span><span class="w"/><span class="n">textField</span><span class="p">.</span><span class="na">getText</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p>Seen in this way, it is a neutral shell, but it will behave towards the outside as a minimal component since the minimum contract via the<strong>Component</strong> interface. Again, only the methods by delegation to the outside are made visible, which explicitly provided. Use is, therefore, harmless.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="kd">public</span><span class="w"/><span class="kd">class</span><span class="nc">MainSolution</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">public</span><span class="w"/><span class="kd">static</span><span class="w"/><span class="kt">void</span><span class="w"/><span class="nf">main</span><span class="w"/><span class="p">(</span><span class="n">String</span><span class="w"/><span class="o">[]</span><span class="w"/><span class="n">args</span><span class="p">)</span><span class="w"/><span class="p">{</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="kd">var</span><span class="w"/><span class="n">inputComponent</span><span class="w"/><span class="o">=</span><span class="w"/><span class="k">new</span><span class="w"/><span class="n">InputComponent</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputComponent</span><span class="p">.</span><span class="na">setText</span><span class="w"/><span class="p">(</span><span class="s">"Hello Text M03"</span><span class="p">);</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="n">inputComponent</span><span class="p">.</span><span class="na">click</span><span class="w"/><span class="p">();</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span><span class="w"/></span></span><span class="line"><span class="cl"><span class="w"/><span class="p">}</span></span></span></code></pre></div></div><p><strong>Targeted Inheritance</strong></p><p>Let&rsquo;s conclude with what I believe is rarely used Java feature at the class level. The speech is about the keyword final.</p><p>To prevent an unintentional derivation, I recommend the targeted use of final classes. Thus, from this point, the unfavourable inheritance on the composition level is troublesome. Understandably, in most frameworks, no use of it was made. After all, you want to allow the user of the component<strong>Button</strong> to offer a specialized version. But at the beginning of your abstraction level, you can very well use it.</p><p><strong>Conclusion</strong></p><p>At this point, we have seen how you can achieve a more robust variant of a composition by delegation rather than inheritance. You can also use this if you are confronted with legacy source codes with this anti-pattern. It&rsquo;s not always possible to clean up everything or change it to the last detail. But I hope this has given an incentive to approach this situation.</p><p>The source code for this example can be found on<a href="https://github.com/Java-Publications/JavaSpektrum_2019-006">GitHub</a>.</p><p><strong>Cheers Sven!</strong></p>
]]></content:encoded><category>Java</category></item><item><title>A Challenge of the Software Distribution</title><link>https://svenruppert.com/posts/a-challenge-of-the-software-distribution/</link><pubDate>Sun, 14 Feb 2021 14:03:57 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/a-challenge-of-the-software-distribution/</guid><description> The four factors that are working against us Software development is more and more dependent on Dependencies and the frequency of deployments is increasing. Both trends together are pushing themselves higher. Another element that turns the delivery of software into a network bottleneck is the usage of compounded artefacts. And the last trend that is working against us, is the exploding amount of edges or better-called edge nodes.All four trends together are a challenge for the infrastructure.But what we could do against it?</description><content:encoded>&lt;![CDATA[<p><a href="https://open.spotify.com/show/0rZHMLs9fWq1G0Q2DAQbc3"><figure><img src="/images/spotify-badge.svg" alt="" loading="lazy" decoding="async"/></a></p><h3 id="the-four-factors-that-are-working-against-us">The four factors that are working against us</h3><p>Software development is more and more dependent on Dependencies and the frequency of deployments is increasing. Both trends together are pushing themselves higher. Another element that turns the delivery of software into a network bottleneck is the usage of compounded artefacts. And the last trend that is working against us, is the exploding amount of edges or better-called edge nodes.All four trends together are a challenge for the infrastructure.But what we could do against it?</p><p><a href="https://youtu.be/VSLZ4Q6ELEk">https://youtu.be/VSLZ4Q6ELEk</a></p><h3 id="edge-computing">Edge-Computing</h3><p>Before we look at the acceleration strategies I will explain a bit the term &ldquo;Edge&rdquo; or better &ldquo;Edge-Computing&rdquo; because this is often used in this context.</p><p><strong>What is Edge or better edge computing?</strong></p><p>The principle of edge computing states that data processing takes place at the Edge of the network. Which device is ultimately responsible for processing the data can differ depending on the application and the implementation of the concept.<br>
An edge device is a device on the network periphery that generates, processes or forwards data itself. Examples of edge devices are smartphones, autonomous vehicles, sensors or IoT devices such as fire alarms.<br>
An edge gateway is installed between the edge device and the network. It receives data from edge devices that do not have to be processed in real-time, processes specific data locally or selectively, sends the data to other services or central data centers. Edge gateways have wireless or wired interfaces to the edge devices and the communication networks for private or public clouds.</p><p><strong>Pros of Edge Computing</strong></p><p>The data processing takes place in the vicinity of the data source, minimising transmission and response times. Communication is possible almost in real-time. Simultaneously, the data throughput and the bandwidth usage reduction in the network, since only specific data that are not to be processed locally need to be transmitted to central data centres. Many functions can also be maintained even if the network or parts of the network fail—the performance of edge computing scales by providing more intelligent devices at the network periphery.</p><p><strong>Cons of Edge Computing</strong></p><p>Edge computing offers more security due to the locally limited data storage, but this is only the case if appropriate security concepts are available for the decentralised devices, due to the heterogeneity and many different devices, the effort involved in implementing the security concepts increases.</p><p><strong>Fog Computing</strong></p><p>Edge computing and fog computing are both decentralised data processing concepts. Fog Computing inserts another layer with the so-called Fog Nodes between the edge devices and the cloud. These are small, local data centres in the access areas of the cloud. These fog nodes collect the data from the edge devices. You select the data to be processed locally or decentrally and forward it to central servers or process it directly yourself.<br>
Selecting the best of both worlds means we are combining both principles of Edge- and Fog-Computing.</p><h3 id="what-are-the-acceleration-options-for-sw-distribution">What are the acceleration options for SW Distribution?</h3><p>There are different strategies to scale the distribution of binaries, and every solution suits a specific use-case. We will not have a few on cloud solutions only because companies are operating worldwide and have to deal with different governmental regulations and restrictions. Additionally, to these restrictions, I want to highlight the need for hybrid solutions as well. Hybrid solutions are including on-prem resources as well as air gaped infrastructure used for high-security environments.</p><p><strong>a) Custom Solution based on replication or scaling servers</strong></p><p>One possibility to scale inside your network/architecture is scaling hardware and working with direct replication. Implementing this by yourself will most-likely consume a higher budget of workforce, knowledge, time and money based on the fact that this is not a trivial project. At the same time, this approach is bound into the borders of the infrastructure you have access to.</p><p><strong>b) P2P Networks</strong></p><p>Peer to Peer networks is based on equal nodes that are sharing the binaries between all nodes.The peer to peer approach implies that you will have a bunch of copies of your files. If you are downloading a file from the network, all nodes can serve parts independently. This approach of splitting up files and delivering from different nodes simultaneously to the requesting node leads to constant and efficient network usage and reduced download times.</p><p><strong>c) CDN - Content Delivery Network</strong></p><p>CDN&rsquo;s are optimised to deliver large files over regions. The network itself is build-out of a huge number of nodes that are caching files for regional delivery. With this strategy, the original server will not be overloaded.</p><div class="code-wrap"><div class="code-wrap__bar"><span class="code-wrap__lang">java</span><button type="button" class="code-wrap__copy" aria-label="Copy code">Copy</button></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">_Check</span><span class="w"/><span class="n">out</span><span class="w"/><span class="n">on</span><span class="w"/><span class="n">my</span><span class="o">**</span><span class="n">Youtube</span><span class="o">**</span><span class="w"/><span class="n">Channel</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">video</span><span class="w"/><span class="n">with</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">title</span><span class="w"/><span class="s">"**DevSecOps - the Low hanging fruits** "</span><span class="p">.</span><span class="na">This</span><span class="w"/><span class="n">video</span><span class="w"/><span class="n">describes</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">balance</span><span class="w"/><span class="n">between</span><span class="w"/><span class="n">writing</span><span class="w"/><span class="n">the</span><span class="w"/><span class="n">code</span><span class="w"/><span class="n">itself</span><span class="w"/><span class="n">or</span><span class="w"/><span class="n">adding</span><span class="w"/><span class="n">a</span><span class="w"/><span class="n">dependency</span><span class="w"/><span class="n">in</span><span class="w"/><span class="n">each</span><span class="w"/><span class="n">Cloud</span><span class="o">-</span><span class="n">Native</span><span class="w"/><span class="n">App</span><span class="w"/><span class="n">layer</span><span class="p">.</span><span class="w"/><span class="n">The</span><span class="w"/><span class="n">question</span><span class="w"/><span class="n">is</span><span class="p">,</span><span class="w"/><span class="n">what</span><span class="w"/><span class="n">does</span><span class="w"/><span class="k">this</span><span class="w"/><span class="n">mean</span><span class="w"/><span class="k">for</span><span class="w"/><span class="n">DevSecOps</span><span class="o">?</span><span class="n">_</span></span></span></code></pre></div></div><h3 id="jfrog-solution">JFrog Solution</h3><p>With the three mentioned techniques you can build up huge and powerful architecture that fit´s to your needs. But the integration of all these technologies and implementing products is not easy. We faced this challenge as well and over the years we found solutions that we integrated into a single DevSecOps Platform called &ldquo;<strong>The JFrog Platform</strong> &ldquo;. I don´t want to give an overview of all components, for this check out my youtube channel. Here I want to focus on the components that are responsible for the Distribution of the binaries only.</p><p><strong>JFrog Distribution</strong></p><p>With the<a href="https://www.jfrog.com/confluence/display/JFROG/JFrog+Distribution">JFrog Distribution</a>, the knowledge about the content of the repositories and the corresponding metadata is used to provide a replication strategy. The replication solution is designed for internal and external repositories to bring the binaries all the way down to the place where it is needed. The infrastructure can be built in a hybrid model, including on-prem and cloud nodes.Even air-gapped solutions are possible with import/export mechanisms. In this scenario, we are focussing on a scalable caching mechanism that is optimised for reads.</p><p><strong>What is a Release Bundle?</strong></p><p>A Release bundle is a composition of binaries. These binaries can be of different types, like maven, Debian or Docker. The<a href="https://www.jfrog.com/confluence/display/JFROG/Distributing+Release+Bundles">Release Bundle</a> can be seen as a Bills Of Materials (BOM).The content and well as the Release Bundles itself are immutable. This immutability makes it possible to implement efficient caching and replication mechanisms across different networks and regions.</p><p><strong>What is an Edge Node in this context?</strong></p><p>An Edge Node in our context is a node that will provide the functionality of a read-only Artifactory.With this Edge Node, the delivery process is optimised, and we will see that replication is done in a transactional way. The difference to the original meaning of an Edge Node is that this instance is not the consuming or producing element. This can be seen as a Fog-Node, that is the first layer above the real edge nodes layer.</p><p><strong>P2P Download</strong></p><p>The<a href="https://www.jfrog.com/confluence/display/JFROG/JFrog+Peer-to-Peer+%28P2P%29+Downloads">P2P solution</a> focuses on environments that need to handle download bursts inside the same network or region.This download bursts could be scenarios like &ldquo;updating a server farm&rdquo; or &ldquo;updating a Microservice Mesh&rdquo;. The usage is unidirectional, which means that the consumer is not updating from their side. They are just waiting for a new version and all consumer updating at the same time.This behaviour is a perfect case for the P2P solution. Artifactory, or an Edge Node in the same network or region, is influencing an update of all P2P Nodes with a new version of a binary. The consumer itself will request the binary from the P2P node and not from the Artifactory instance anymore.The responsible Artifactory instance manages the P2P nodes, which leads to zero maintenance on the user side. Have in mind, that the RBAC is active at the P2P nodes as well.</p><p><strong>CDN Distribution</strong></p><p>The CDN Solution is optimised to deliver binaries to different parts of the world. We have it in two flavours. One is for the public and mostly used to distribute SDK&rsquo;s, Drivers or other free available binaries. The other flavour is focussing on the private distribution.Whatever solution you are using, the RBAC defined inside the Access Module is respected, including solutions with Authentication and Authorisation and unique links including Access Tokens.</p><h3 id="conclusion">Conclusion</h3><p>Ok, it is time for the conclusion.What we discussed today;<br>
With the increasing amount of dependencies, a higher frequency of deployments and the constantly growing number of applications and edge-nodes, we are facing scalability challenges.<br>
We had a look at three ways you could go to increase your delivery speed.The discussed solution based on</p><p><strong>a)</strong> JFrog Distribution helps you build up a strong replication strategy inside your hybrid infrastructure to speed up the development cycle.<br><strong>b)</strong> JFrog P2P that will allow you to handle massive download bursts inside a network or region. This solution fits tasks that need to distribute binaries to a high number of consumers concurrently during download bursts.<br><strong>c)</strong> JFrog CDN to deliver binaries worldwide into regional data centres to make the experience for the consumer as best as possible.</p><p>All this is bundled into the JFrog DevSecOps Platform.</p><p>Cheers Sven</p>
]]></content:encoded><category>DevSecOps</category></item><item><title>DevSecOps - Be Independent Again</title><link>https://svenruppert.com/posts/devsecops-be-independent-again/</link><pubDate>Fri, 12 Feb 2021 18:06:03 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/devsecops-be-independent-again/</guid><description> What do the effects the news of the last few months can have to do with risk management and the presumption of storage, and why is it an elementary component of DevSecOps?</description><content:encoded>&lt;![CDATA[<p><a href="https://open.spotify.com/show/0rZHMLs9fWq1G0Q2DAQbc3"><figure><img src="/images/spotify-badge.svg" alt="" loading="lazy" decoding="async"/></a></p><p>What do the effects the news of the last few months can have to do with risk management and the presumption of storage, and why is it an elementary component of DevSecOps?</p><hr><p><strong>If you want to see this Post as a video, check-out the following from me Youtube Channel</strong></p><p><a href="https://youtu.be/nXxOXgRY5_s">https://youtu.be/nXxOXgRY5_s</a></p><hr><h2 id="what-has-happened-so-far">What Has Happened So Far</h2><p>Again and again, changes have happened that set things in motion that were considered to have been set. In some cases, services or products have been freely available for many years, or the type of restriction has not changed. I am taking one of the last changes as an occasion to show the resulting behavior and to formulate solutions that help you deal with it.</p><p>In software development, repositories are one of the central elements that enable you to efficiently deal with the abundance of dependencies in a software environment. A wide variety of types and associated technologies have evolved over the decades. But there is a common approach mostly resulted in a global central authority that is seen as an essential reference.</p><p>**I examined the topic of the repository from a generic point of view in a little more detail on youtube. **</p><p><a href="https://youtu.be/Wk-rlZ904to">https://youtu.be/Wk-rlZ904to</a></p><p>As an example, I would like to briefly show what a minimal technology stack can look like today. Java is used for the application itself, the dependencies of which are defined using<strong>maven</strong>. For this, we need access to maven repositories. Debian repositories [<a href="https://youtu.be/TqxdLOs0Q1E">Why Debian Repos are mission-critical..</a>] used for the operating system on which the application is running. The components that then packaged into Docker images use Docker registries, and finally, the applications orchestrated in a composition of Docker images using Kubernetes. Here alone, we are dealing with four different repository types. At this point, I have left out the need for generic repositories to provide the required tools used within the DevSecOps pipeline.</p><figure><img src="/images/2021/01/2020-004-001.png" alt="" loading="lazy" decoding="async"/><h3 id="dockerhub-and-its-dominance">DockerHub And Its Dominance</h3><p>The example that inspired me to write this article was DockerHub&rsquo;s announcements. Access to this service was free, and there were no further restrictions on storage space and storage duration for freely available Docker images. This fact has led to a large number of open source projects using this repository for their purposes. Over the years a whole network of dependencies between these images has built up.</p><p>Docker Hub was in the news recently for two reasons.</p><h4 id="storage-restrictions">Storage Restrictions</h4><p>Previously, Docker images were stored indefinitely on Dockerhub. On the one hand, this meant that nobody cared about the storage space of the Docker images. On the other hand, pretty much everyone has been counting on it not to change again. Unfortunately, that has now changed. The retention period for inactive Docker images has been reduced to six months. What doesn&rsquo;t sound particularly critical at first turns out to be quite uncomfortable in detail.</p><h4 id="download-throttling">Download Throttling</h4><p>Docker has limited the download rate to 100 pulls per six hours for anonymous users, and 200 pulls per six hours for free accounts. Number 200 sounds pretty bearable. However, it makes sense to take a more detailed look here. 200 inquiries / 6h are 200 inquiries / 360min. We&rsquo;re talking about 0.55 queries/minute at a constant query rate. First, many systems do more than one build and therefore requests, every 2 minutes. Second, if the limit is reached, it can take more than half a business day to regain access. The latter is to be viewed as very critical. As a rule, limit values given per hour, which then only leads to a delay of a little less than an hour. Six hours is a different order of magnitude.</p><h3 id="maven-and-mavencentral">Maven and MavenCentral</h3><p>If you look at the different technologies, a similar monoculture emerges in the Maven area. Here is the maven-central a singular point operated by one company. A larger company bought this company. What does this mean for the future of this repository? I don&rsquo;t know. However, it is not uncommon for costs to be optimized after a takeover by another company. A legitimate question arises here; What economic advantage does the operator of such a central, free-of-charge infrastructure have?</p><h3 id="jdks">JDKs</h3><p>There have been so many structural changes here that I&rsquo;m not even sure what the official name is. But there is one thing I observe with eagle eyes in projects. Different versions, platforms and providers of the JDKs result in a source of joy in LTS projects that should not be underestimated. Here, too, it is not guaranteed how long the providers will keep the respective builds of a JDK for a platform. What is planned today can be optimized tomorrow. Here, too, you should take a look at the JDKs that are not only used internally but also by customers. Who has all the installers for the JDKs in use in stock? Are these JDKs also used within your own CI route, or do you trust the availability of specific Docker images?</p><h2 id="moderate-independence">Moderate Independence</h2><p>How can this be countered now? The answer is straightforward. You get everything you need just once and then save it in your systems. And so we are running against the efforts of the last few years. As in most other cases, moderate use of this approach is recommended. More important than ever is the sensible use of freely available resources. It can help if a stringent retention tactic is used. Not everything has to be kept indefinitely. Many elements that are held in the caches are no longer needed after a while. Sophisticated handling of repositories and the nesting of resources helps here. Unfortunately, I cannot go into too much detail here, but it can be noted in short form.</p><p>The structure of the respective repositories enables, on the one hand, to create concrete compositions and, on the other hand, to carry out very efficient maintenance. Sources must be kept in individual repositories and then merged using virtual repositories. This process can be built up so efficiently that it can even drastically reduce the number of build cycles.</p><h2 id="devsecops---risk-minimization">DevSecOps - Risk Minimization</h2><p>There is another advantage in dealing with the subject of &ldquo;independence&rdquo;. Because all files that are kept in their structures can be analyzed with regard to vulnerabilities and compliance, now when these elements are in one place, in a repository manager, I have a central location where I can scan them. The result is a complete dependency graph that includes the dependencies of an application but also the associated workbench. That, in turn, is one of the critical statements when you turn to the topic of DevSecOps. Security is like quality! It&rsquo;s not just a tool; it&rsquo;s not just a person responsible for it. It is a philosophy that has to run through the entire value chain.</p><p>Happy Coding,</p><p>Sven Ruppert</p>
]]></content:encoded><category>DevSecOps</category></item><item><title>The quick Wins of DevSecOps</title><link>https://svenruppert.com/posts/the-quick-wins-of-devsecops/</link><pubDate>Thu, 28 Jan 2021 16:56:45 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/posts/the-quick-wins-of-devsecops/</guid><description>Hello and welcome to my DevSecOps post. Here in Germany, it&rsquo;s winter right now, and the forests are quiet. The snow slows down everything, and it&rsquo;s a beautiful time to move undisturbed through the woods.</description><content:encoded>&lt;![CDATA[<p>Hello and welcome to my DevSecOps post. Here in Germany, it&rsquo;s winter right now, and the forests are quiet. The snow slows down everything, and it&rsquo;s a beautiful time to move undisturbed through the woods.</p><p>Here you can pursue your thoughts, and I had to think about a subject that customers or participants at conferences ask me repeatedly.</p><p><strong>The question is almost always:</strong></p><p><em>What are the quick wins or low hanging fruits if you want to deal more with the topic of security in software development?</em></p><p>And I want to answer this question right<strong>now!</strong></p><p><strong>For the lazy ones, you can see it as youtube video as well</strong></p><p><a href="https://www.youtube.com/embed/lNqADishl8w">https://www.youtube.com/embed/lNqADishl8w</a></p><p>Let&rsquo;s start with the definition of a phrase that often used in the business world.</p><h2 id="make-or-buy">Make Or Buy</h2><p>Even as a software developer, you will often hear this phrase during meetings with the company&rsquo;s management and sales part.</p><p>The phrase is called; &ldquo;<strong>Make or Buy</strong> &ldquo;. Typically, we have to decide if we want to do something ourselves or spend money to buy the requested functionality. It could be less or more functionality or different so that we have to adjust ourself to use it in our context.</p><p>But as a software developer, we have to deal with the same question every day. I am talking about dependencies. Should we write the source code by ourselves or just adding the next dependencies? Who will be responsible for removing bugs, and what is the total cost of this decision? But first, let&rsquo;s take a look at the make-or-buy association inside the full tech-stack.</p><h2 id="diff-between-make--buy-on-all-layers">Diff between Make / Buy on all layers.</h2><p>If we are looking at all layers of a cloud-native stack to compare the value of &ldquo;make&rdquo; to &ldquo;buy&rdquo; we will see that the component &ldquo;buy&rdquo; is in all layers the bigger one. But first things first.</p><p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--W7psDFBb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xrncrgwe5wnoms4g7fdp.jpg"><figure><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--W7psDFBb--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/xrncrgwe5wnoms4g7fdp.jpg" alt="Alt Text" loading="lazy" decoding="async"/></a></p><p>The first step is the development of the application itself.</p><p>Assuming that we are working with Java and using maven as a dependency manager, we are most likely adding more lines of code indirectly as dependency compared to the number of lines we are writing by ourselves. The dependencies are the more prominent part, and third parties develop them. We have to be carefully, and it is good advice to check these external binaries for known vulnerabilities.</p><p>We should have the same behaviour regarding compliance and license usage. The next layer will be the operating system, in our case Linux.</p><p>And again, we are adding some configuration files and the rest are existing binaries.</p><p>The result is an application running inside the operating system that is a composition of external binaries based on our configuration.</p><p>The two following layers, Docker and Kubernetes, are leading us to the same result. Until now, we are not looking at the tool-stack for the production line itself.</p><p>All programs and utilities that are directly or indirectly used under the hood called DevSecOps are some dependencies.</p><p>All layers&rsquo; dependencies are the most significant part by far.</p><p>Checking these binaries against known Vulnerabilities is the first logical step.</p><h2 id="one-time-and-recurring-efforts-for-compliancevulnerabilities">one time and recurring efforts for Compliance/Vulnerabilities</h2><p>Comparing the effort of scanning against known Vulnerabilities and for Compliance Issues, we see a few differences.</p><p>Let&rsquo;s start with the Compliance issues.</p><p><strong>Compliance issues:</strong></p><p>The first step will be defining what licenses are allowed at what part of the production line. This definition of allowed license includes the dependencies during the coding time and the usage of tools and runtime environments. Defining the non-critical license types should be checked by a specialised lawyer. With this list of white labelled license types, we can start using the machine to scan on a regular base the full tool stack. After the machine found a violation, we have to remove this element, and it must be replaced by another that is licensed under a white-labelled one.</p><p><strong>Vulnerabilities:</strong></p><p>The recurrent effort on this site is low compared to the amount of work that vulnerabilities are producing. A slightly different workflow is needed for the handling of found vulnerabilities. Without more significant preparations, the machine can do the work on a regular base as well. The identification of a vulnerability will trigger the workflow that includes human interaction. The vulnerability must be classified internally that leads to the decisions what the following action will be.</p><h3 id="compliance-issues-just-singular-points-in-your-full-stack">Compliance Issues: just singular points in your full-stack</h3><p>There is one other difference between Compliance Issues and Vulnerabilities. If there is a compliance issue, it is a singular point inside the overall environment. Just this single part is a defect and is not influencing other elements of the environment.</p><p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gIRlbb-S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/65coi40ktdrd8267zl0l.jpg"><figure><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gIRlbb-S--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/65coi40ktdrd8267zl0l.jpg" alt="Alt Text" loading="lazy" decoding="async"/></a></p><h3 id="vulnerabilities-can-be-combined-into-different-attack-vectors">Vulnerabilities: can be combined into different attack vectors.</h3><p>Vulnerabilities are a bit different. They do not only exist at the point where they are located. Additionally, they can be combined with other existing vulnerabilities in any additional layer of the environment. Vulnerabilities can be combined into different attack vectors. Every possible attack vector itself must be seen and evaluated. A set of minor vulnerabilities in different layers of the application can be combined into a highly critical risk.</p><p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EyLw_MNa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5wlzxpr0si3q6de2l3ax.jpg"><figure><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EyLw_MNa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/5wlzxpr0si3q6de2l3ax.jpg" alt="Alt Text" loading="lazy" decoding="async"/></a></p><h2 id="vulnerabilities-timeline-from-found-until-active-in-the-production">Vulnerabilities: timeline from found until active in the production</h2><p>I want to have an eye on as next is the timeline from vulnerability is found until the fix is in production. After a vulnerability is existing in a binary, we have nearly no control over the time until this is found. It depends on the person itself if the vulnerability is reported to the creator of the binary, a commercial security service, a government or it will be sold on a darknet marketplace. But, assuming that the information is reported to the binary creator itself, it will take some time until the data is publicly available. We have no control over the duration from finding the vulnerability to the time that the information is publicly available. The next period is based on the commercial aspect of this issue.</p><div class="pull-quote"><p>As a consumer, we can only get the information as soon as possible is spending money.<br>
This state of affairs is not nice, but mostly the truth.</p></div><p>Nevertheless, at some point, the information is consumable for us. If you are using<strong>JFrog Xray</strong> , from the free tier, for example, you will get the information very fast.<strong>JFrog</strong> is consuming different security information resources and merging all information into a single vulnerability database. After this database is fed with new information, all<strong>JFrog Xray</strong> instances are updated. After this stage is reached, you can act.</p><p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--lyCCm79w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/38n00jl7shtaxwidiyat.jpg"><figure><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--lyCCm79w--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/38n00jl7shtaxwidiyat.jpg" alt="Alt Text" loading="lazy" decoding="async"/></a></p><h2 id="test-coverage-is-your-safety-belt-try-mutation-testing">Test-Coverage is your safety-belt; try Mutation Testing.</h2><p>Until now, the only thing you can do to speed up the information flow is spending money for professional security information aggregator. But as soon as the information is consumable for you, the timer runs. It depends on your environment how fast this security fix will be up and running in production. To minimise the amount of time a full automated CI Pipeline ist one of the critical factors.</p><p>But even more critical is excellent and robust test coverage.</p><p>Good test coverage will allow you, to switch dependency versions immediately and push this change after a green test run into production. I recommend using a more substantial test coverage as pure line-coverages. The technique called &ldquo;mutation test coverage&rdquo; is a powerful one.</p><p><strong>Mutation Test Coverage</strong><br>
If you want to know more about this on, check out my YouTube channel. I have a video that explains the theoretical part and the practical one for Java and Kotlin.</p><p><a href="https://www.youtube.com/embed/6Vej7YEOF8g">https://www.youtube.com/embed/6Vej7YEOF8g</a></p><p>The need for a single point that understands all repo-types<br>
To get a picture of the full impact graph based on all known vulnerabilities, it is crucial to understand all package managers included by the dependencies. Focussing on just one layer in the tech-stack is by far not enough.</p><p><strong>JFrog Artifactory</strong> provides information, including the vendor-specific metadata that is part of the package managers.</p><p><strong>JFrog Xray</strong> can consume all this knowledge and can scan all binaries that are hosted inside the repositories that are managed by Artifactory.</p><p><a href="https://res.cloudinary.com/practicaldev/image/fetch/s--zNq_iO_T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hhnlxa5e3enzhuwtf2yl.jpg"><figure><img src="https://res.cloudinary.com/practicaldev/image/fetch/s--zNq_iO_T--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/hhnlxa5e3enzhuwtf2yl.jpg" alt="Alt Text" loading="lazy" decoding="async"/></a></p><h2 id="vulnerabilities---ide-plugin">Vulnerabilities - IDE plugin</h2><p>Shift Left means that Vulnerabilities must be eliminated as early as possible inside the production pipeline. One early-stage after the concept phase is the coding itself. At the moment you start adding dependencies to your project you are possibly adding Vulnerabilities as well.</p><p>The fastest way to get feedback regarding your dependencies is the<strong>JFrog IDE Plugin</strong>. This plugin will connect your IDE to your JFrog Xray Instance. The free tier will give you access to Vulnerability scanning. The Plugin is OpenSource and available for IntelliJ, VS-Code, Eclipse,&hellip; If you need some additional features, make a feature request on GitHub or fork the Repository add your changes and make a merge request.</p><div class="pull-quote"><p><strong>Try it out by yourself -<a href="https://jfrog.com/artifactory/start-free/">JFrog Free Tier</a></strong></p></div><h2 id="how-to-use-the-ide-plugin">How to use the IDE plugin?</h2><p>If you add a dependency to your project, the IDE Plugin can understand this information based on the used package manager. The IDE Plugin is connected to your JFrog Xray instance and will be queried if there is a change inside your project&rsquo;s dependency definition. The information provided by Xray includes the known vulnerabilities of the added dependency. If there is a fixed version of the dependency available, the new version number will be shown.</p><p><strong>If you want to see the IDE Plugin in Action without registering</strong></p><p><strong>for a Free Tier, have a look at my youtube video.</strong></p><p><a href="https://www.youtube.com/embed/PsghzAf-ODU">https://www.youtube.com/embed/PsghzAf-ODU</a></p><h2 id="conclusion">Conclusion</h2><p>With the JFrog Free Tier, you have the tools in your hands to practice Shift Left and pushing it into your IDE.</p><p>Create repositories for all included technologies, use Artifactory as a proxy for your binaries and let Xray scan the full stack.</p><p>With this, you have a complete impact graph based on your full-stack and the pieces of information about known Vulnerabilities as early as possible inside your production line.</p><p>You don&rsquo;t have to wait until your CI Pipeline starts complaining. This will save a lot of your time.</p>
]]></content:encoded><category>DevSecOps</category><category>Java</category><category>TDD</category></item><item><title>Search</title><link>https://svenruppert.com/search/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>sven.ruppert@gmail.com (Sven Ruppert)</author><dc:creator>Sven Ruppert</dc:creator><guid isPermaLink="true">https://svenruppert.com/search/</guid><description/><content:encoded>&lt;![CDATA[
]]></content:encoded></item></channel></rss>