<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: mohamed anas charkaoui</title>
    <description>The latest articles on DEV Community by mohamed anas charkaoui (@mohamed_anascharkaoui_b9).</description>
    <link>https://dev.to/mohamed_anascharkaoui_b9</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3050462%2F4e6a3860-c33c-48e4-a636-023a5d313641.png</url>
      <title>DEV Community: mohamed anas charkaoui</title>
      <link>https://dev.to/mohamed_anascharkaoui_b9</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohamed_anascharkaoui_b9"/>
    <language>en</language>
    <item>
      <title>The "Invisible" Server Bug: What Building a Bare-Metal Java Server Taught Me About I/O</title>
      <dc:creator>mohamed anas charkaoui</dc:creator>
      <pubDate>Sun, 12 Jul 2026 01:00:27 +0000</pubDate>
      <link>https://dev.to/mohamed_anascharkaoui_b9/the-invisible-server-bug-what-building-a-bare-metal-java-server-taught-me-about-io-6fi</link>
      <guid>https://dev.to/mohamed_anascharkaoui_b9/the-invisible-server-bug-what-building-a-bare-metal-java-server-taught-me-about-io-6fi</guid>
      <description>&lt;p&gt;This is the first article in a series where I document building an HTTP server in Java from raw sockets — no frameworks, no Tomcat, no Spring. Just &lt;code&gt;java.net&lt;/code&gt; and whatever mistakes I make along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I Started
&lt;/h2&gt;

&lt;p&gt;Week one looked like this — everything crammed into a single &lt;code&gt;main()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;App&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServerSocket&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ServerSocket&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InetSocketAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"the server is ready"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Socket&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Server accepted a new client. Processing..."&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

                &lt;span class="nc"&gt;PrintWriter&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrintWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BufferedWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
                        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OutputStreamWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOutputStream&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
                    &lt;span class="o"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HTTP/1.1 200 OK"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type: text/html"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Length: 53"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;Bare-Metal is UP!&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"error in the main method with :"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ugly, but it worked. One accepted connection, one hand-built response, &lt;code&gt;flush()&lt;/code&gt; and &lt;code&gt;close()&lt;/code&gt; both there, done.&lt;/p&gt;

&lt;p&gt;Then I started splitting this apart — pulling connection handling into its own &lt;code&gt;ClientWorker&lt;/code&gt; class, and request parsing into a &lt;code&gt;Dispatcher&lt;/code&gt;, to get ready for a thread pool and actual routing. That refactor is where I hit a bug that took me a genuinely embarrassing amount of time to find.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Bug
&lt;/h2&gt;

&lt;p&gt;The new &lt;code&gt;ClientWorker&lt;/code&gt; looked, at a glance, like a clean translation of the same logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;BufferedReader&lt;/span&gt; &lt;span class="n"&gt;br&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;BufferedReader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;InputStreamReader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInputStream&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;br&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;readLine&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Dispatcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Rout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nc"&gt;PrintWriter&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrintWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BufferedWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OutputStreamWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOutputStream&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code compiled. It ran. It accepted connections without a single exception. And the browser just spun on a blank screen, waiting forever for a response that never came.&lt;/p&gt;

&lt;p&gt;The bug: I wrote the response with &lt;code&gt;pw.println(res)&lt;/code&gt;, then closed the raw &lt;code&gt;client&lt;/code&gt; socket directly — never touching &lt;code&gt;pw&lt;/code&gt; again. In the Week 1 version, &lt;code&gt;flush()&lt;/code&gt; and &lt;code&gt;close()&lt;/code&gt; were both sitting right there, two lines below the write. Somewhere in pulling the logic apart into a separate class, they quietly didn't make the trip.&lt;/p&gt;

&lt;p&gt;To explain why forgetting two lines could freeze an entire response, I need to explain why this three-layer nested constructor exists in the first place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;PrintWriter&lt;/span&gt; &lt;span class="n"&gt;pw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;PrintWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;BufferedWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OutputStreamWriter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getOutputStream&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The "Over-Engineered" Syntax That's Actually Genius
&lt;/h2&gt;

&lt;p&gt;The first time you see this in Java, it looks like a joke. Three constructors nested inside each other just to write text to a socket? Why isn't there one simple &lt;code&gt;NetworkWriter&lt;/code&gt; class that does all of this?&lt;/p&gt;

&lt;p&gt;It turns out this isn't over-engineering — it's the direct, deliberate answer to a problem that would otherwise collapse a codebase under its own weight. To see why, you have to understand what I/O actually costs, and what happens if you try to avoid that cost the naive way.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I/O Actually Means
&lt;/h2&gt;

&lt;p&gt;I/O stands for Input/Output. In code, an I/O operation is any action that forces your program to step outside the memory the operating system assigned to it.&lt;/p&gt;

&lt;p&gt;When your program touches a local variable, it's operating inside fast, safe RAM that belongs to it. But the moment you want to read a file off disk or send a packet over the network, your program has to hand off to the OS kernel and talk to actual hardware. That handoff — crossing from your process into the kernel and back — is slow. Not "slightly slower," but orders of magnitude slower than working in RAM. This is where Java's design problem starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Nightmare of Class Explosion
&lt;/h2&gt;

&lt;p&gt;Say you want a utility that writes data to a file. You build &lt;code&gt;FileByteWriter&lt;/code&gt;. It works.&lt;/p&gt;

&lt;p&gt;A week later, someone wants to write text &lt;code&gt;String&lt;/code&gt;s instead of raw bytes. Now you need &lt;code&gt;FileStringWriter&lt;/code&gt; too.&lt;/p&gt;

&lt;p&gt;A few days after that, the app needs to write to a network socket instead of a file. Now you need &lt;code&gt;SocketByteWriter&lt;/code&gt; and &lt;code&gt;SocketStringWriter&lt;/code&gt;. You're at four classes for two features.&lt;/p&gt;

&lt;p&gt;Now imagine product wants encryption (SSL). And compression (GZIP). And buffering for performance. If you keep building one dedicated class per combination, your class count grows exponentially — roughly 2ⁿ for n independent features. You'd eventually need hundreds of classes like &lt;code&gt;EncryptedBufferedSocketStringWriter&lt;/code&gt;, and half your day would go to remembering which one you actually need.&lt;/p&gt;

&lt;p&gt;This is called &lt;strong&gt;class explosion&lt;/strong&gt;, and it's exactly what Java's I/O designers built around using the &lt;strong&gt;Decorator Pattern&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Java's Actual Blueprint: Three Layers You Stack
&lt;/h2&gt;

&lt;p&gt;Instead of one class per combination, Java splits I/O into small, single-purpose pieces you compose like building blocks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Connection Layer&lt;/strong&gt; — the actual physical endpoint, like &lt;code&gt;File&lt;/code&gt; or &lt;code&gt;Socket&lt;/code&gt;. It only knows how to move raw bytes in or out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The Transport Layer&lt;/strong&gt; — converts between representations. A raw socket only understands bytes; wrapping it in &lt;code&gt;OutputStreamWriter&lt;/code&gt; gives you a translator that turns your characters into the bytes the network actually needs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The Performance/Feature Layer&lt;/strong&gt; — the decorators you stack on top. This is where &lt;code&gt;BufferedWriter&lt;/code&gt; comes in.&lt;/p&gt;

&lt;p&gt;Sending data one byte at a time over a network is like sending a semi-truck to deliver a single kitchen sponge — technically it works, but you're paying for a full trip every single time. A buffer is a small warehouse: it holds your data in RAM until there's a full truckload (commonly 8KB), then ships it all in one trip.&lt;/p&gt;

&lt;p&gt;Then &lt;code&gt;PrintWriter&lt;/code&gt; goes on top of all of that, giving you convenient methods like &lt;code&gt;println()&lt;/code&gt; so you can write clean text without thinking about any of the layers underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where My Bug Actually Came From
&lt;/h2&gt;

&lt;p&gt;Here's the part that isn't in most tutorials: &lt;strong&gt;the buffer doesn't know your response is "done."&lt;/strong&gt; It only knows two things — "am I full?" and "was I told to flush?" That's it.&lt;/p&gt;

&lt;p&gt;My response was small — a few hundred bytes, nowhere close to the 8KB buffer threshold — so the buffer just sat there, holding my data, waiting for either more bytes to arrive or an explicit signal that would never come. Then I closed the raw socket directly, out from under the writer. The connection went away with the data still sitting in the buffer. From the browser's side, nothing had errored — it just never received a response, so it kept waiting.&lt;/p&gt;

&lt;p&gt;The fix was two lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;pw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;flush()&lt;/code&gt; tells the buffer "stop waiting for a full truckload, ship what you have right now." &lt;code&gt;close()&lt;/code&gt; does something similar internally — a &lt;code&gt;PrintWriter&lt;/code&gt;'s &lt;code&gt;close()&lt;/code&gt; flushes automatically before it shuts down — which is why in real code you'll often see people rely on &lt;code&gt;close()&lt;/code&gt; alone, or better, a try-with-resources block, since either one guarantees the flush happens even if an exception is thrown partway through. What actually broke my server was closing the &lt;em&gt;socket&lt;/em&gt; directly and never touching the writer at all, so neither of those safety nets ever ran.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the "Ugly" Syntax Is Actually a Feature
&lt;/h2&gt;

&lt;p&gt;The nested-constructor syntax isn't hiding this complexity from you — it's making every layer, and every decision, visible and swappable. Want to drop buffering because you need every byte to go out immediately (like a live streaming protocol)? Delete one line. Want to add compression? Wrap one more layer around the existing ones, and nothing else in your code has to change.&lt;/p&gt;

&lt;p&gt;That's the Open-Closed Principle in practice: the system is open for extension, but closed for modification. You don't touch Java's stream, buffering, or formatting logic — you just compose new pieces on top of what's already there.&lt;/p&gt;

&lt;p&gt;What looks like over-engineering on first read is actually one of the more resilient, composable designs in the standard library — and understanding &lt;em&gt;why&lt;/em&gt; it's built that way is what turned an invisible, silent bug into two obvious lines of code.&lt;/p&gt;




&lt;p&gt;*This is Part 1 of a series on building an HTTP server in Java from scratch. Part 2 covers what happened when I hit this same server with concurrent requests — and why "just add more RAM" doesn't fix what actually breaks. Code for this series is on GitHub at &lt;a href="https://github.com/AnasMAC/java-baremetal-server/releases/tag/v1-blocking-server" rel="noopener noreferrer"&gt;https://github.com/AnasMAC/java-baremetal-server/releases/tag/v1-blocking-server&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Scaling Architecture, Not Hardware: Building a Multi-Threaded NIO Web Server from Scratch in Java</title>
      <dc:creator>mohamed anas charkaoui</dc:creator>
      <pubDate>Sun, 12 Jul 2026 00:40:29 +0000</pubDate>
      <link>https://dev.to/mohamed_anascharkaoui_b9/scaling-architecture-not-hardware-building-a-multi-threaded-nio-web-server-from-scratch-in-java-4pma</link>
      <guid>https://dev.to/mohamed_anascharkaoui_b9/scaling-architecture-not-hardware-building-a-multi-threaded-nio-web-server-from-scratch-in-java-4pma</guid>
      <description>&lt;p&gt;Why doesn't throwing more CPU and RAM at your server solve your latency issues under heavy load?&lt;/p&gt;

&lt;p&gt;In this article, we will see why buying a more powerful server will not increase the performance of your blocking I/O app. Instead of just throwing hardware at the problem, we are going to get our hands dirty with systems architecture and build a custom, multi-threaded Non-Blocking I/O (NIO) HTTP server in Java from scratch. And yes, we will talk about the painful concurrency bugs you run into when you try to build the parts a framework normally hides from you.&lt;/p&gt;

&lt;p&gt;This is Part 2 of a series where I document building an HTTP server in Java from raw sockets. Part 1 covers a buffering bug in blocking I/O — this one covers what happened under concurrent load and the NIO rewrite that followed.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 1: The Trap of Blocking I/O (BIO)
&lt;/h2&gt;

&lt;p&gt;Imagine you are at a restaurant where one waiter is assigned to one table. When the waiter takes your order, they walk to the kitchen and stand there staring at the wall while the chef cooks your meal. While that waiter is waiting, no other customer in the restaurant can be served. If the restaurant gets busy, the owner's only solution is to hire more waiters. Eventually, the kitchen gets overcrowded, salaries skyrocket, and the restaurant runs out of physical space.&lt;/p&gt;

&lt;p&gt;This is exactly how Blocking I/O (BIO) works.&lt;/p&gt;

&lt;p&gt;In a classic blocking Java server (using &lt;code&gt;ServerSocket&lt;/code&gt;), every incoming TCP connection gets its own dedicated OS thread. When a thread tries to read from a socket, it blocks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// The thread is now frozen here until the client decides to send something&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;bytesRead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inputStream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the client has slow internet, or if your database query takes a few seconds, that thread is just sitting there wasting resources.&lt;/p&gt;

&lt;p&gt;If you think, "I'll just buy a bigger server with more RAM and spin up more threads," here is the catch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Thread Overhead:&lt;/strong&gt; In Java, each thread takes up about 1MB of memory for its stack. If you have 5,000 active connections, that is roughly 5GB of RAM gone just to keep idle threads sleeping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context Switching:&lt;/strong&gt; The CPU spends more time swapping between thousands of threads than actually running your code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bottlenecks:&lt;/strong&gt; Your database connection pool or OS files will max out long before your fancy CPU even hits 50%.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Non-Blocking (NIO) Solution
&lt;/h3&gt;

&lt;p&gt;Instead of one waiter per table, imagine a single waiter with a pager system. The waiter takes orders from multiple tables, sends them to the kitchen, and moves on to serve other people. When a dish is ready, the kitchen buzzes the pager. The waiter only works when there is an actual event to handle.&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 2: Migrating from BIO to Java NIO
&lt;/h2&gt;

&lt;p&gt;When we migrate from Blocking I/O to Non-Blocking I/O in Java, we swap out our core networking objects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ServerSocket&lt;/code&gt; becomes &lt;code&gt;ServerSocketChannel&lt;/code&gt;: We can configure it to be non-blocking by calling &lt;code&gt;ssc.configureBlocking(false)&lt;/code&gt;. This lets us listen for connection events without freezing the thread.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Socket&lt;/code&gt; becomes &lt;code&gt;SocketChannel&lt;/code&gt;: Reads and writes become non-blocking. Calling &lt;code&gt;read()&lt;/code&gt; returns immediately with whatever data is currently ready.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To understand how this works, we need to talk about Channels, Buffers, and Selectors.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Channels (SocketChannel)
&lt;/h3&gt;

&lt;p&gt;Think of a channel as a two-way pipe. Unlike standard blocking streams, you can tell it to be non-blocking. If you ask it to read, it returns immediately: it gives you the bytes if they are there, a &lt;code&gt;0&lt;/code&gt; if the network is still loading, or &lt;code&gt;-1&lt;/code&gt; if the client closed the connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Buffers (ByteBuffer)
&lt;/h3&gt;

&lt;p&gt;You cannot read directly from a channel. You must read from the channel into a buffer, and write from a buffer into the channel.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;ByteBuffer&lt;/code&gt; is not like a normal array or list. It has three pointers that you have to manage manually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;capacity&lt;/code&gt;: The size of the buffer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;position&lt;/code&gt;: Where you are currently reading or writing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;limit&lt;/code&gt;: The boundary of how far you can go.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because there is only one pointer (&lt;code&gt;position&lt;/code&gt;) for both reading and writing, you have to tell the buffer when you are switching roles by calling &lt;code&gt;flip()&lt;/code&gt; and &lt;code&gt;clear()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To show you what is actually happening under the hood, here is a simple conceptual class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MockByteBuffer&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MockByteBuffer&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="o"&gt;];&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. Write mode: putting data into the buffer&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;++]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Flip: Switch from WRITE mode to READ mode&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;flip&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Limit becomes the end of written data&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Reset pointer to start reading from the beginning&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Clear: Switch from READ mode back to WRITE mode&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;     &lt;span class="c1"&gt;// Reset pointer to the start for writing&lt;/span&gt;
        &lt;span class="n"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Restore limit to full capacity&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;When you write bytes into the buffer (like receiving data from a socket), the position moves forward.&lt;/li&gt;
&lt;li&gt;Before you read those bytes, you call &lt;code&gt;flip()&lt;/code&gt;. This resets the position to &lt;code&gt;0&lt;/code&gt; and sets the limit to where you stopped writing, so you don't read empty space.&lt;/li&gt;
&lt;li&gt;Once you are done reading, you call &lt;code&gt;clear()&lt;/code&gt; to reset the pointer so you can write into it again.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Selectors
&lt;/h3&gt;

&lt;p&gt;The Selector is the brain of NIO. It is the multiplexer that monitors multiple channels.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You register your channel to a selector and tell it what you are interested in (like &lt;code&gt;SelectionKey.OP_READ&lt;/code&gt; for incoming data or &lt;code&gt;SelectionKey.OP_ACCEPT&lt;/code&gt; for new connections).&lt;/li&gt;
&lt;li&gt;You call &lt;code&gt;selector.select()&lt;/code&gt;, which blocks your thread only until one of those events happens.&lt;/li&gt;
&lt;li&gt;Then you get the &lt;code&gt;selectedKeys()&lt;/code&gt; and process them one by one.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Chapter 3: Architecting the Event Loop (Boss &amp;amp; Workers)
&lt;/h2&gt;

&lt;p&gt;For our server, we are going to implement a simple Boss-Worker model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Boss Thread (NioServer.java):&lt;/strong&gt; Its only job is to run a selector that listens for new client connections (&lt;code&gt;OP_ACCEPT&lt;/code&gt;). When a client connects, it accepts the channel, sets it to non-blocking, and hands it over to the worker pool.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Worker Pool (SelectorsPool.java):&lt;/strong&gt; A pool of threads, each running its own selector. They handle the reading (&lt;code&gt;OP_READ&lt;/code&gt;) and processing for the clients assigned to them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is how the Boss Thread sets up the &lt;code&gt;ServerSocketChannel&lt;/code&gt; and binds it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ServerSocketChannel&lt;/span&gt; &lt;span class="n"&gt;ssc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ServerSocketChannel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ssc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;InetSocketAddress&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;ssc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;configureBlocking&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Make it non-blocking&lt;/span&gt;

        &lt;span class="nc"&gt;Selector&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;ssc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SelectionKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OP_ACCEPT&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Listen only for accepts&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Block until a client connects&lt;/span&gt;
            &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SelectionKey&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;selectedKeys&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="nc"&gt;Iterator&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SelectionKey&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;iterator&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hasNext&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;SelectionKey&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isAcceptable&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                    &lt;span class="nc"&gt;SocketChannel&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ssc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;accept&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
                    &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;configureBlocking&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                    &lt;span class="c1"&gt;// Pass the channel to our worker pool!&lt;/span&gt;
                    &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; 
                &lt;span class="o"&gt;}&lt;/span&gt;
                &lt;span class="n"&gt;iter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;remove&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error in the nio server: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Chapter 4: Processing the Data (NioWorker.java)
&lt;/h2&gt;

&lt;p&gt;Once the client is registered to a worker's selector, the worker thread reads the bytes and routes the request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NioWorker&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Runnable&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;SocketChannel&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;Dispatcher&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;NioWorker&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SocketChannel&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Dispatcher&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;dispatcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;ByteBuffer&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ByteBuffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allocate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="nc"&gt;StringBuilder&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StringBuilder&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
            &lt;span class="c1"&gt;// Read bytes from channel into buffer&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;flip&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Switch to read mode&lt;/span&gt;
                &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;append&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;array&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;StandardCharsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;UTF_8&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
                &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clear&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Clear buffer for next write&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toString&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

            &lt;span class="c1"&gt;// Route the request and write the response back&lt;/span&gt;
            &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rout&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;split&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"\n"&lt;/span&gt;&lt;span class="o"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;]);&lt;/span&gt;
            &lt;span class="nc"&gt;ByteBuffer&lt;/span&gt; &lt;span class="n"&gt;resBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ByteBuffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wrap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
            &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resBuffer&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;close&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; 
        &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error in the worker: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Known simplification: this version reads once per worker invocation and assumes the full request arrived in that pass — it doesn't yet register back with a selector to wait for more &lt;code&gt;OP_READ&lt;/code&gt; events if a client's request is split across multiple TCP packets. That's a real gap between this code and the "worker has its own selector" model described above, and it's on my list to fix in a later revision rather than something I'm claiming is solved here.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Chapter 5: The Hidden Trap of Custom NIO
&lt;/h2&gt;

&lt;p&gt;Building your own web server is great for learning, but it exposes you to subtle bugs that modern frameworks hide. Let's look at one of the biggest headaches you will encounter:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Selector Registration Deadlock
&lt;/h3&gt;

&lt;p&gt;Look at how the Boss hands a socket to a worker's selector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Boss Thread:&lt;/span&gt;
&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workerSelector&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SelectionKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OP_READ&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And look at how the Worker is waiting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Worker Thread:&lt;/span&gt;
&lt;span class="n"&gt;workerSelector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;select&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// Blocks here waiting for network activity&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In Java, &lt;code&gt;selector.select()&lt;/code&gt; locks the selector's key set. At the same time, calling &lt;code&gt;socketChannel.register(selector, ...)&lt;/code&gt; from the Boss thread tries to get that exact same lock.&lt;/p&gt;

&lt;p&gt;If the worker thread is blocked inside &lt;code&gt;select()&lt;/code&gt;, the Boss thread will hang forever on &lt;code&gt;.register()&lt;/code&gt;, and your entire server will freeze.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Fix
&lt;/h3&gt;

&lt;p&gt;You have to wake up the selector before registering the channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;workerSelector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;wakeup&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workerSelector&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;SelectionKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;OP_READ&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: Because &lt;code&gt;wakeup()&lt;/code&gt; and &lt;code&gt;register()&lt;/code&gt; are not atomic, production frameworks like Netty use a concurrent queue to enqueue registration tasks and run them directly inside the worker thread loop, avoiding this lock contention entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Benchmarks: Proving the Architecture
&lt;/h2&gt;

&lt;p&gt;Same test every time — an artificial 5-second delay in the request handler to simulate a slow database call, hit with &lt;code&gt;ab -n 10 -c 10 http://localhost:8080/&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Server&lt;/th&gt;
&lt;th&gt;Total time (10 requests, concurrency 10)&lt;/th&gt;
&lt;th&gt;Requests/sec&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 thread, blocking (no pool)&lt;/td&gt;
&lt;td&gt;50.015s&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 NIO worker + boss&lt;/td&gt;
&lt;td&gt;50.015s&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 NIO workers + boss&lt;/td&gt;
&lt;td&gt;10.009s&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 1-worker NIO server and the 1-thread blocking server land on almost identical numbers — which makes sense, since one worker means every request still queues behind the last one, regardless of blocking or non-blocking I/O underneath. The moment you add nine more workers, ten requests that would've taken 50 seconds serially finish in about 10 — a 5x improvement from spreading load across workers, not from any change in hardware.&lt;/p&gt;

&lt;p&gt;(I also ran this with a 10-thread blocking pool instead of one thread, which is the more common real-world setup — I don't have that exact run saved, so I'm leaving it out rather than guessing at a number. Worth noting though: since 10 threads can serve 10 concurrent requests at once, that version should land close to the single request's 5-second delay rather than 50 seconds — the same idea as the NIO version, just paying for it in threads instead of workers.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion &amp;amp; Takeaways
&lt;/h2&gt;

&lt;p&gt;Writing a bare-metal web server teaches you three main things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Architecture &amp;gt; Hardware:&lt;/strong&gt; Buying a bigger server is useless if your system is bottlenecked by blocking threads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NIO is Hard:&lt;/strong&gt; Managing buffers, selector states, and thread deadlocks manually is highly complex.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Appreciate the Ecosystem:&lt;/strong&gt; This project makes you appreciate why frameworks like Netty, Tomcat, and Spring Boot exist. They handle the low-level heavy lifting so you can focus on writing actual business features.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.youtube.com/watch?v=wB9tIg209-8&amp;amp;t=196s" rel="noopener noreferrer"&gt;"Non-blocking I/O and how Node uses it, in friendly terms"&lt;/a&gt; by Studying With Alex — a good visual walkthrough of the blocking vs. non-blocking distinction.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.baeldung.com/java-nio-selector" rel="noopener noreferrer"&gt;Baeldung's guide to Java NIO Selectors&lt;/a&gt; — a deeper dive into managing selector keys.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Code for this article is tagged at &lt;a href="https://github.com/AnasMAC/java-baremetal-server/" rel="noopener noreferrer"&gt;https://github.com/AnasMAC/java-baremetal-server/&lt;/a&gt; — that snapshot matches exactly what's described above. The &lt;code&gt;main&lt;/code&gt; branch has since grown to include a database connection pool, dependency injection, and server-side rendering, which are their own upcoming articles in this series.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>programming</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
