<?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: Mustafa Bingül</title>
    <description>The latest articles on DEV Community by Mustafa Bingül (@bingulhan).</description>
    <link>https://dev.to/bingulhan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1195654%2F0c4d3a96-03aa-4cec-8e5b-36bb43109690.png</url>
      <title>DEV Community: Mustafa Bingül</title>
      <link>https://dev.to/bingulhan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bingulhan"/>
    <language>en</language>
    <item>
      <title>I got tired of heavyweight frameworks, so I built my own web server in Java</title>
      <dc:creator>Mustafa Bingül</dc:creator>
      <pubDate>Fri, 24 Apr 2026 20:30:58 +0000</pubDate>
      <link>https://dev.to/bingulhan/i-got-tired-of-heavyweight-frameworks-so-i-built-my-own-web-server-in-java-2k76</link>
      <guid>https://dev.to/bingulhan/i-got-tired-of-heavyweight-frameworks-so-i-built-my-own-web-server-in-java-2k76</guid>
      <description>&lt;h1&gt;
  
  
  I got tired of heavyweight frameworks, so I built my own web server in Java
&lt;/h1&gt;

&lt;p&gt;Most Java web projects start with Spring Boot. You add the dependency, and suddenly you have an entire ecosystem loaded into memory before your first route is even registered. For a lot of use cases that's totally fine. But I wanted something I could drop a JAR into a folder, double-click, and have a running web server in under three seconds — no config ceremony, no classpath drama.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Aurelius&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is it?
&lt;/h2&gt;

&lt;p&gt;Aurelius is a lightweight, minimalist Java web server designed for developers who want to get a site or a small API running without pulling in a full framework. It runs on Java 8+, ships as a single executable JAR (or &lt;code&gt;.exe&lt;/code&gt; on Windows), and is built on top of Netty for the networking layer.&lt;/p&gt;

&lt;p&gt;The core idea: drop the JAR in a folder, launch it, put your HTML files in the right place, and you have a running server. That's it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project structure
&lt;/h2&gt;

&lt;p&gt;When Aurelius starts, it expects this layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-project/
├── aurelius.jar
├── settings.yml
├── app/
│   └── main.html        ← served at /
│   └── about/
│       └── main.html    ← served at /about
├── containers/
│   └── button.html
│   └── section.html
└── public/
    └── logo.png
    └── data.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every folder under &lt;code&gt;app/&lt;/code&gt; becomes a route. &lt;code&gt;main.html&lt;/code&gt; in a folder is the index for that route. Files under &lt;code&gt;public/&lt;/code&gt; are served statically and accessible from any HTML file via &lt;code&gt;/public/filename&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The container system
&lt;/h2&gt;

&lt;p&gt;This is the part I'm most proud of. Instead of reaching for a templating engine, I built a simple component system directly into the server.&lt;/p&gt;

&lt;p&gt;You define a reusable HTML fragment in the &lt;code&gt;containers/&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- containers/button.html --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"btn btn-primary"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;{text}&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you use it anywhere in your pages with a custom tag:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;container.button&lt;/span&gt; &lt;span class="na"&gt;text=&lt;/span&gt;&lt;span class="s"&gt;"Click me"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/container.button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Aurelius processes the tag at serve time, injects the attribute values into the fragment, and sends the assembled HTML to the client. No JavaScript required, no build step, no npm.&lt;/p&gt;

&lt;p&gt;You can nest containers, pass multiple attributes, and compose entire page sections from reusable fragments. It's not React — it's intentionally much simpler — but for static-ish sites it removes a lot of repetition.&lt;/p&gt;

&lt;h2&gt;
  
  
  Placeholder system
&lt;/h2&gt;

&lt;p&gt;For site-wide variables, Aurelius has a &lt;code&gt;placeholders.yml&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;My&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Site"&lt;/span&gt;
&lt;span class="na"&gt;author&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mustafa"&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Anywhere in your HTML you write &lt;code&gt;%title%&lt;/code&gt; and Aurelius substitutes it at serve time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;%title% — Home&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is useful for things like site name, meta descriptions, or any string you'd otherwise duplicate across dozens of pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  RESTful API support via addons
&lt;/h2&gt;

&lt;p&gt;Aurelius isn't just for serving HTML. You can register REST endpoints programmatically through the addon system:&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;// GET /api/hello&lt;/span&gt;
&lt;span class="nc"&gt;AddonManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;registerRestFulService&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;RestFulResponseStructure&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Builder&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hello"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setRestFulResponse&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Hello, world! Path: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nc"&gt;Arrays&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="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPathData&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="na"&gt;setRequestType&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;RestFulRequestType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GET&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&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;For structured request/response bodies, you implement the &lt;code&gt;RestFulResponse&amp;lt;Output, Input&amp;gt;&lt;/code&gt; interface and Aurelius handles JSON deserialization automatically:&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;UserController&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;RestFulResponse&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;UserDto&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;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="nc"&gt;UserDto&lt;/span&gt; &lt;span class="nf"&gt;response&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RestFulResponseHelper&lt;/span&gt; &lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;UserDto&lt;/span&gt; &lt;span class="n"&gt;dto&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;UserDto&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;dto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUsername&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getUsername&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;dto&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="nc"&gt;UserRequest&lt;/span&gt; &lt;span class="nf"&gt;convert&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;body&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;UserRequest&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;AddonManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;convertFromBodyJson&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;UserRequest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&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;Cookies are also supported through the helper:&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;CookieStructure&lt;/span&gt; &lt;span class="n"&gt;cookie&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;CookieStructure&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCookieName&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"session"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setCookieValue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UUID&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;randomUUID&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="n"&gt;cookie&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setFeatures&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Arrays&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;asList&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;CFMaxAge&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3600&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;CFHttpOnly&lt;/span&gt;&lt;span class="o"&gt;()));&lt;/span&gt;
&lt;span class="n"&gt;helper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sendCookie&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;All server settings live in &lt;code&gt;settings.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
  &lt;span class="na"&gt;threadSize&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
  &lt;span class="na"&gt;ui&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ui: true&lt;/code&gt; enables a built-in management panel where you can start, stop, and reload the server without touching the terminal. Useful if you're deploying somewhere without a great CLI experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Framework support
&lt;/h2&gt;

&lt;p&gt;Because Aurelius just serves HTML, it works with any CSS framework that can be loaded from a CDN. Tailwind, Bootstrap, Bulma — just drop the &lt;code&gt;&amp;lt;link&amp;gt;&lt;/code&gt; tag in your HTML and it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who is this for?
&lt;/h2&gt;

&lt;p&gt;Aurelius is a good fit if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Want a zero-dependency web server for a small internal tool or personal site&lt;/li&gt;
&lt;li&gt;Are learning Java web development and want to see how a server works without framework magic hiding everything&lt;/li&gt;
&lt;li&gt;Need to prototype a REST API quickly without standing up a Spring application&lt;/li&gt;
&lt;li&gt;Want to understand Netty's HTTP handling without writing the boilerplate yourself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's not the right tool for large production applications with complex routing, authentication middleware, or ORM integration. For those, reach for Spring Boot or Quarkus.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The main thing on my list is proper test coverage — the container parsing and placeholder substitution logic especially need unit tests. I'm also thinking about WebSocket support and a simple session management API.&lt;/p&gt;

&lt;p&gt;11 releases in, Aurelius handles the basics solidly. If you're the kind of developer who likes understanding what's running under the hood, give it a try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/mustafabinguldev/Aurelius" rel="noopener noreferrer"&gt;https://github.com/mustafabinguldev/Aurelius&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Example project: &lt;a href="https://github.com/mustafabinguldev/aurelius-example-project" rel="noopener noreferrer"&gt;https://github.com/mustafabinguldev/aurelius-example-project&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live site: &lt;a href="https://aureliusweb.vercel.app" rel="noopener noreferrer"&gt;https://aureliusweb.vercel.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you build something with it or have feedback on the container system design, I'd love to hear from you in the comments.&lt;/p&gt;

</description>
      <category>java</category>
      <category>webserver</category>
      <category>opensource</category>
      <category>beginners</category>
    </item>
    <item>
      <title>I built a centralized data orchestration engine for distributed Minecraft servers — here's how</title>
      <dc:creator>Mustafa Bingül</dc:creator>
      <pubDate>Fri, 24 Apr 2026 20:28:44 +0000</pubDate>
      <link>https://dev.to/bingulhan/i-built-a-centralized-data-orchestration-engine-for-distributed-minecraft-servers-heres-how-4dka</link>
      <guid>https://dev.to/bingulhan/i-built-a-centralized-data-orchestration-engine-for-distributed-minecraft-servers-heres-how-4dka</guid>
      <description>&lt;h1&gt;
  
  
  I built a centralized data orchestration engine for distributed Minecraft servers
&lt;/h1&gt;

&lt;p&gt;This post is about a real architectural problem I ran into while working on a multi-server Minecraft network — and how I solved it by building a dedicated data microservice called &lt;strong&gt;Nexus Core&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;A large Minecraft network runs many game servers at the same time: lobby servers, PvP arenas, survival worlds, minigame instances. Each one is a separate JVM process running a Spigot plugin. The traditional approach is simple — every server connects directly to MongoDB, manages its own in-memory cache, and handles its own data logic.&lt;/p&gt;

&lt;p&gt;This works fine at small scale. At larger scale, three problems emerge:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Connection pool exhaustion.&lt;/strong&gt; Each server holds its own pool of MongoDB connections. With 20 servers, you're throwing 20× the connections at your database for no good reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache incoherency.&lt;/strong&gt; Server A caches a player's balance. Server B modifies it. Server A's cache is now stale. The player sees incorrect data depending on which server they're on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicated logic.&lt;/strong&gt; The same data access code exists in every plugin. Changing how player data is structured means touching 15 codebases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The solution: one service that owns all data
&lt;/h2&gt;

&lt;p&gt;Nexus Core is a standalone Java application that acts as the single source of truth for all data in the network. Game servers never query MongoDB directly. Instead, they publish a structured packet to a Redis channel, and Nexus Core handles the rest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Spigot #1 ─┐
Spigot #2 ──► Redis (pub/sub) ──► Nexus Core ──► MongoDB
Spigot #3 ─┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A single, optimized MongoDB connection pool&lt;/li&gt;
&lt;li&gt;A centralized Redis cache that all servers read from&lt;/li&gt;
&lt;li&gt;Data logic defined once per data type, shared everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The packet protocol
&lt;/h2&gt;

&lt;p&gt;Every request from a game server is a JSON packet published to a Redis channel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"protocol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pvp-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GET_DATA"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"uuid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"550e8400-e29b-41d4-a716-446655440000"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;protocol&lt;/code&gt; is a numeric ID that identifies which data type is being requested. &lt;code&gt;source&lt;/code&gt; is the server's identifier, used to publish the response back to the right channel. &lt;code&gt;type&lt;/code&gt; is one of &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;SET&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, or &lt;code&gt;GET_ALL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Nexus Core subscribes to the inbound channel, deserializes the packet, and routes it to the right handler in O(1) via a &lt;code&gt;Map&amp;lt;Integer, DataAddon&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The DataAddon abstraction
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;DataAddon&lt;/code&gt; is an abstract class that you extend to define a new data type. It maps to exactly one MongoDB collection and one Redis cache namespace. Here's what a minimal addon looks like:&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;PlayerStatsAddon&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;DataAddon&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;int&lt;/span&gt; &lt;span class="nf"&gt;addonId&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;          &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;100&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="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;addonName&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;     &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Player Stats"&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="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getDatabase&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;   &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"nexus_core_db"&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="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getCollection&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"player_stats"&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="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;cacheKeyHeaderTag&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"stats"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="nd"&gt;@DbDataModels&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isId&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="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@DbDataModels&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isId&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="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;kills&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@DbDataModels&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;isId&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="kd"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;deaths&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;boolean&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&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;source&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RequestType&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;NexusJsonDataContainer&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// return false to reject the request&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;Nexus Core discovers the schema at boot time via reflection over &lt;code&gt;@DbDataModels&lt;/code&gt; annotations. It finds which field is the primary key, reads default values for missing fields, and knows how to serialize/deserialize documents without you writing any mapping code.&lt;/p&gt;

&lt;p&gt;Registering the addon is one line:&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;NexusApplication&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getProtocolHandler&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;registerAddon&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;PlayerStatsAddon&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From that point on, any game server can send a packet with &lt;code&gt;protocol: 100&lt;/code&gt; and get a full player stats document back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Request lifecycle
&lt;/h2&gt;

&lt;p&gt;Here's the full flow for a &lt;code&gt;GET&lt;/code&gt; request:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Game server publishes packet to Redis&lt;/li&gt;
&lt;li&gt;Nexus Core receives it and looks up the addon by protocol ID&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;handleRequest()&lt;/code&gt; is called — if it returns &lt;code&gt;false&lt;/code&gt;, an empty response is sent back immediately&lt;/li&gt;
&lt;li&gt;Nexus Core checks Redis for key &lt;code&gt;stats:{uuid}&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Cache hit → serialize and publish response&lt;/li&gt;
&lt;li&gt;Cache miss → query MongoDB, write result to Redis, publish response&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;SET&lt;/code&gt; writes through to both MongoDB and Redis. &lt;code&gt;DELETE&lt;/code&gt; removes from both. &lt;code&gt;GET_ALL&lt;/code&gt; bypasses cache entirely — caching arbitrary result sets would make invalidation logic very complex.&lt;/p&gt;

&lt;h2&gt;
  
  
  The handleRequest() interceptor
&lt;/h2&gt;

&lt;p&gt;This is one of my favorite parts of the design. Before any database operation happens, your addon's &lt;code&gt;handleRequest()&lt;/code&gt; method is called synchronously. You can inspect the source server, the request type, and the payload — and reject the request outright by returning &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is where authorization lives. For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="nf"&gt;handleRequest&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;source&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;RequestType&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;NexusJsonDataContainer&lt;/span&gt; &lt;span class="n"&gt;data&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;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nc"&gt;RequestType&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;REMOVE_DATA&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"admin"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// only admin server can delete&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep this method fast — it runs synchronously and blocks the request pipeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring dashboard
&lt;/h2&gt;

&lt;p&gt;Nexus Core ships with a live monitoring UI built entirely in Java Swing with Java2D — no external libraries. It shows real-time JVM CPU load, process RAM usage, number of objects currently in the Redis cache, and the count of active addons. All rendered as animated donut charts and scrolling line graphs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;p&gt;Looking back, the main thing missing is test coverage. The addon system and cache invalidation logic are exactly the kind of thing that benefits from solid unit tests, and I didn't write them. That's the next thing I'm adding.&lt;/p&gt;

&lt;p&gt;I'd also consider replacing the protocol integer with a string identifier — numeric IDs work fine but require a separate constants file to stay readable, which is an avoidable friction point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/mustafabinguldev/nexus-core" rel="noopener noreferrer"&gt;https://github.com/mustafabinguldev/nexus-core&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're building anything on distributed Java infrastructure or have thoughts on the addon model, I'd love to hear from you in the comments.&lt;/p&gt;

</description>
      <category>java</category>
      <category>architecture</category>
      <category>redis</category>
      <category>mongodb</category>
    </item>
  </channel>
</rss>
